diff --git a/qrenderdoc/Code/FormatElement.cpp b/qrenderdoc/Code/FormatElement.cpp index 5fb1b9ab5..e690b30ac 100644 --- a/qrenderdoc/Code/FormatElement.cpp +++ b/qrenderdoc/Code/FormatElement.cpp @@ -111,6 +111,12 @@ static QVariant interpret(const ResourceFormat &f, byte comp) return QVariant(); } +struct StructFormatData +{ + QList elems; + uint32_t offset = 0; +}; + FormatElement::FormatElement() { buffer = 0; @@ -144,7 +150,11 @@ FormatElement::FormatElement(const QString &Name, int buf, uint offs, bool perIn QList FormatElement::ParseFormatString(const QString &formatString, uint64_t maxLen, bool tightPacking, QString &errors) { - QList elems; + StructFormatData root; + StructFormatData *cur = &root; + + QMap structelems; + QString lastStruct; // regex doesn't account for trailing or preceeding whitespace, or comments @@ -167,8 +177,8 @@ QList FormatElement::ParseFormatString(const QString &formatStrin ")" // 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 - "(\\[[0-9]+\\])?" // optional array dimension + "(\\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 "$")); @@ -177,22 +187,104 @@ QList FormatElement::ParseFormatString(const QString &formatStrin QString text = formatString; - text = text.replace(lit("{"), QString()).replace(lit("}"), QString()); - QRegularExpression c_comments(lit("/\\*[^*]*\\*+(?:[^*/][^*]*\\*+)*/")); QRegularExpression cpp_comments(lit("//.*")); text = text.replace(c_comments, QString()).replace(cpp_comments, QString()); - uint32_t offset = 0; + 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 + "\\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(QLatin1Char(';'))) + 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 = &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]; + continue; + } + } + + QRegularExpressionMatch structMatch = structUseRegex.match(line); + + if(structMatch.hasMatch() && structelems.contains(structMatch.captured(1))) + { + StructFormatData &structDef = structelems[structMatch.captured(1)]; + + QString varName = structMatch.captured(2); + + QString arrayDim = structMatch.captured(3).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; + } + + // inline use of this struct in the current parent + for(uint32_t arrayIdx = 0; arrayIdx < arrayCount; arrayIdx++) + { + for(const FormatElement &templ : structDef.elems) + { + FormatElement el = templ; + el.name = arrayCount > 1 ? QFormatStr("%1[%2].%3").arg(varName).arg(arrayIdx).arg(el.name) + : QFormatStr("%1.%2").arg(varName).arg(el.name); + el.offset += cur->offset; + + cur->elems.push_back(el); + } + + cur->offset += structDef.offset; + } + + continue; + } + QRegularExpressionMatch match = regExpr.match(line); if(!match.hasMatch()) @@ -217,6 +309,7 @@ QList FormatElement::ParseFormatString(const QString &formatStrin } ResourceFormat fmt; + fmt.type = ResourceFormatType::Regular; fmt.compType = CompType::Typeless; bool hex = false; @@ -380,7 +473,7 @@ QList FormatElement::ParseFormatString(const QString &formatStrin if(arrayCount == 1) { - FormatElement elem(name, 0, offset, false, 1, row_major, matrixCount, fmt, hex, rgb); + FormatElement elem(name, 0, cur->offset, false, 1, row_major, matrixCount, fmt, hex, rgb); uint32_t advance = elem.byteSize(); @@ -391,33 +484,33 @@ QList FormatElement::ParseFormatString(const QString &formatStrin // cbuffer packing doesn't allow elements to cross float4 boundaries, nudge up if this was // the case - if(offset / 16 != (offset + elem.byteSize() - 1) / 16) + if(cur->offset / 16 != (cur->offset + elem.byteSize() - 1) / 16) { - elem.offset = offset = (offset + 0xFU) & (~0xFU); + elem.offset = cur->offset = (cur->offset + 0xFU) & (~0xFU); } } - elems.push_back(elem); + cur->elems.push_back(elem); - offset += advance; + cur->offset += advance; } else { // when cbuffer packing, arrays are always aligned at float4 boundary if(!tightPacking) { - if(offset % 16 != 0) + if(cur->offset % 16 != 0) { - offset = (offset + 0xFU) & (~0xFU); + cur->offset = (cur->offset + 0xFU) & (~0xFU); } } for(uint a = 0; a < arrayCount; a++) { - FormatElement elem(QFormatStr("%1[%2]").arg(name).arg(a), 0, offset, false, 1, row_major, - matrixCount, fmt, hex, rgb); + FormatElement elem(QFormatStr("%1[%2]").arg(name).arg(a), 0, cur->offset, false, 1, + row_major, matrixCount, fmt, hex, rgb); - elems.push_back(elem); + cur->elems.push_back(elem); uint32_t advance = elem.byteSize(); @@ -427,14 +520,19 @@ QList FormatElement::ParseFormatString(const QString &formatStrin advance = (advance + 0xFU) & (~0xFU); } - offset += advance; + cur->offset += advance; } } } - if(!success || elems.isEmpty()) + // if we succeeded parsing but didn't get any root elements, use the last defined struct as the + // definition + if(success && root.elems.isEmpty() && !lastStruct.isEmpty()) + root = structelems[lastStruct]; + + if(!success || root.elems.isEmpty()) { - elems.clear(); + root.elems.clear(); ResourceFormat fmt; fmt.compType = CompType::UInt; @@ -446,10 +544,10 @@ QList FormatElement::ParseFormatString(const QString &formatStrin if(maxLen > 0 && maxLen < 4) fmt.compByteWidth = 1; - elems.push_back(FormatElement(lit("data"), 0, 0, false, 1, false, 1, fmt, true, false)); + root.elems.push_back(FormatElement(lit("data"), 0, 0, false, 1, false, 1, fmt, true, false)); } - return elems; + return root.elems; } QString FormatElement::GenerateTextureBufferFormat(const TextureDescription &tex) diff --git a/qrenderdoc/Widgets/BufferFormatSpecifier.ui b/qrenderdoc/Widgets/BufferFormatSpecifier.ui index 15d01c945..fe8296432 100644 --- a/qrenderdoc/Widgets/BufferFormatSpecifier.ui +++ b/qrenderdoc/Widgets/BufferFormatSpecifier.ui @@ -75,7 +75,7 @@ - Type in a buffer format declaration. Comments and {} braces are skipped, : semantics are ignored. + Type in a buffer format declaration, including struct definitions. Comments and {} braces are skipped, : semantics are ignored. Declare each element as an hlsl/glsl variable, e.g: "float4 first; float2 second; uint2 third;" or vec4/vec2. Basic types accepted: bool, byte, short, int, half, float, double. @@ -86,7 +86,7 @@ Additionally special formats: unorm[hb] (half, byte) and snorm[hb], uintten/unor You can prepend 'rgb' onto an element to colour backgrounds with the RGB values, e.g. 'rgb xbyte4 pixels[256]'. -Vectors (e.g. float4), matrices ([row_major] half3x4) and arrays (float[16]) are supported. +Vectors (e.g. float4), matrices ([row_major] half3x4) and single-dimension arrays (float[16]) are supported. true diff --git a/qrenderdoc/Windows/PipelineState/PipelineStateViewer.cpp b/qrenderdoc/Windows/PipelineState/PipelineStateViewer.cpp index 41b9fc2f7..9f3b2459e 100644 --- a/qrenderdoc/Windows/PipelineState/PipelineStateViewer.cpp +++ b/qrenderdoc/Windows/PipelineState/PipelineStateViewer.cpp @@ -614,55 +614,47 @@ void PipelineStateViewer::setMeshViewPixmap(RDLabel *meshView) }); } -QString PipelineStateViewer::formatMembers(int indent, int requiredByteStride, - const QString &nameprefix, - const rdcarray &vars) +QString PipelineStateViewer::declareStruct(QList &declaredStructs, const QString &name, + const rdcarray &members, + uint32_t requiredByteStride) { - QString indentstr(indent * 4, QLatin1Char(' ')); - QString ret; - int i = 0; + ret = lit("struct %1\n{\n").arg(name); - for(const ShaderConstant &v : vars) + for(int i = 0; i < members.count(); i++) { - if(!v.type.members.empty()) - { - if(i > 0) - ret += lit("\n"); + QString arraySize; + if(members[i].type.descriptor.elements > 1) + arraySize = QFormatStr("[%1]").arg(members[i].type.descriptor.elements); - if(v.type.descriptor.name == "struct") - ret += indentstr + lit("// struct\n"); - else - ret += indentstr + lit("// struct %1\n").arg(v.type.descriptor.name); + QString varTypeName = members[i].type.descriptor.name; - ret += indentstr + lit("{\n") + formatMembers(indent + 1, v.type.descriptor.arrayByteStride, - v.name + lit("_"), v.type.members) + - indentstr + lit("}\n"); - if(i < vars.count() - 1) - ret += lit("\n"); - } - else + if(!members[i].type.members.isEmpty()) { - QString arr; - if(v.type.descriptor.elements > 1) - arr = QFormatStr("[%1]").arg(v.type.descriptor.elements); - ret += QFormatStr("%1%2 %3%4%5;\n") - .arg(indentstr) - .arg(v.type.descriptor.name) - .arg(nameprefix) - .arg(v.name) - .arg(arr); + // 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; + } } - i++; + ret += QFormatStr(" %1 %2%3;\n").arg(varTypeName).arg(members[i].name).arg(arraySize); } if(requiredByteStride > 0) { uint32_t structStart = 0; - const ShaderConstant *lastChild = &vars.back(); + const ShaderConstant *lastChild = &members.back(); structStart += lastChild->byteOffset; while(!lastChild->type.members.isEmpty()) @@ -691,9 +683,11 @@ QString PipelineStateViewer::formatMembers(int indent, int requiredByteStride, uint32_t padBytes = requiredByteStride - (lastChild->byteOffset + size); if(padBytes > 0) - ret += indentstr + padding(padBytes); + ret += lit(" ") + padding(padBytes); } + ret += lit("}\n"); + return ret; } @@ -705,54 +699,65 @@ QString PipelineStateViewer::GenerateBufferFormatter(const ShaderResource &res, if(!res.variableType.members.empty()) { - const rdcarray *members = &res.variableType.members; - - uint32_t requiredStride = 0; - if(m_Ctx.APIProps().pipelineType == GraphicsAPI::Vulkan || m_Ctx.APIProps().pipelineType == GraphicsAPI::OpenGL) { - // 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) + const rdcarray &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 += lit("// %1 members skipped as they are fixed size:\n").arg(res.name); - for(int i = 0; i < members->count() - 1; i++) + 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()) { - QString arraySize; - if(members->at(i).type.descriptor.elements > 1) - arraySize = QFormatStr("[%1]").arg(members->at(i).type.descriptor.elements); - format += QFormatStr("// %1 %2%3;\n") - .arg(members->at(i).type.descriptor.name) - .arg(members->at(i).name) - .arg(arraySize); + comment = arraySize = QString(); + + 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"); } - format += lit("// final array struct @ byte offset %1\n{\n").arg(members->back().byteOffset); - - baseByteOffset += members->back().byteOffset; - } - else - { - format += lit("// struct %1\n{\n").arg(res.name); + format += + QFormatStr(" %1%2 %3%4;\n").arg(comment).arg(varTypeName).arg(members[i].name).arg(arraySize); } - // if the last member is a struct, remove one level of indirection before declaring the - // repeated structure - if(!members->back().type.members.isEmpty()) - { - requiredStride = members->back().type.descriptor.arrayByteStride; + format += lit("}"); - members = &members->back().type.members; + // if the last member is a struct, declare it + if(!members.back().type.members.isEmpty()) + { + QList declaredStructs; + format = declareStruct(declaredStructs, varTypeName, members.back().type.members, + members.back().type.descriptor.arrayByteStride) + + lit("\n") + format; } } else { - format = lit("// struct %1\n{\n").arg(res.variableType.descriptor.name); + QList declaredStructs; + format = declareStruct(declaredStructs, res.variableType.descriptor.name, + res.variableType.members, 0); } - - format += formatMembers(1, requiredStride, QString(), *members); - format += lit("}"); } else { diff --git a/qrenderdoc/Windows/PipelineState/PipelineStateViewer.h b/qrenderdoc/Windows/PipelineState/PipelineStateViewer.h index f3d7d378d..0864cd143 100644 --- a/qrenderdoc/Windows/PipelineState/PipelineStateViewer.h +++ b/qrenderdoc/Windows/PipelineState/PipelineStateViewer.h @@ -93,8 +93,8 @@ private: QMenu *editMenus[6] = {}; - QString formatMembers(int indent, int requiredByteStride, const QString &nameprefix, - const rdcarray &vars); + QString declareStruct(QList &declaredStructs, const QString &name, + const rdcarray &members, uint32_t requiredByteStride); QString GenerateHLSLStub(const ShaderReflection *shaderDetails, const QString &entryFunc); IShaderViewer *EditShader(ResourceId id, ShaderStage shaderType, const rdcstr &entry,