Support specifying structs in buffer formatter. Closes #805

* Structs and arrays-of-structs are completely flattened for the purposes of
  displaying them still.
This commit is contained in:
baldurk
2019-02-14 11:24:26 +00:00
parent f8d490d9e6
commit e9e42839c3
4 changed files with 196 additions and 93 deletions
+120 -22
View File
@@ -111,6 +111,12 @@ static QVariant interpret(const ResourceFormat &f, byte comp)
return QVariant();
}
struct StructFormatData
{
QList<FormatElement> 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> FormatElement::ParseFormatString(const QString &formatString, uint64_t maxLen,
bool tightPacking, QString &errors)
{
QList<FormatElement> elems;
StructFormatData root;
StructFormatData *cur = &root;
QMap<QString, StructFormatData> structelems;
QString lastStruct;
// regex doesn't account for trailing or preceeding whitespace, or comments
@@ -167,8 +177,8 @@ QList<FormatElement> 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> 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> FormatElement::ParseFormatString(const QString &formatStrin
}
ResourceFormat fmt;
fmt.type = ResourceFormatType::Regular;
fmt.compType = CompType::Typeless;
bool hex = false;
@@ -380,7 +473,7 @@ QList<FormatElement> 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> 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> 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> 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)
+2 -2
View File
@@ -75,7 +75,7 @@
<item row="0" column="0" colspan="2">
<widget class="QLabel" name="helpText">
<property name="text">
<string>Type in a buffer format declaration. Comments and {} braces are skipped, : semantics are ignored.
<string>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: &quot;float4 first; float2 second; uint2 third;&quot; 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.</string>
Vectors (e.g. float4), matrices ([row_major] half3x4) and single-dimension arrays (float[16]) are supported.</string>
</property>
<property name="wordWrap">
<bool>true</bool>
@@ -614,55 +614,47 @@ void PipelineStateViewer::setMeshViewPixmap(RDLabel *meshView)
});
}
QString PipelineStateViewer::formatMembers(int indent, int requiredByteStride,
const QString &nameprefix,
const rdcarray<ShaderConstant> &vars)
QString PipelineStateViewer::declareStruct(QList<QString> &declaredStructs, const QString &name,
const rdcarray<ShaderConstant> &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<ShaderConstant> *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<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 += 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<QString> 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<QString> declaredStructs;
format = declareStruct(declaredStructs, res.variableType.descriptor.name,
res.variableType.members, 0);
}
format += formatMembers(1, requiredStride, QString(), *members);
format += lit("}");
}
else
{
@@ -93,8 +93,8 @@ private:
QMenu *editMenus[6] = {};
QString formatMembers(int indent, int requiredByteStride, const QString &nameprefix,
const rdcarray<ShaderConstant> &vars);
QString declareStruct(QList<QString> &declaredStructs, const QString &name,
const rdcarray<ShaderConstant> &members, uint32_t requiredByteStride);
QString GenerateHLSLStub(const ShaderReflection *shaderDetails, const QString &entryFunc);
IShaderViewer *EditShader(ResourceId id, ShaderStage shaderType, const rdcstr &entry,