/****************************************************************************** * The MIT License (MIT) * * Copyright (c) 2016-2026 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 { // the actual definition (including structs pulled in recursively) ShaderConstant structDef; // the line in the format where each member was defined, in case we find a problem later and want // to attach the error to it QList lineMemberDefs; uint32_t pointerTypeId = 0; uint32_t offset = 0; uint32_t alignment = 0; uint32_t paddedStride = 0; // is this a struct definition with [[single]] ? bool singleDef = false; // does this contain a member annotated with [[single]] ? bool singleMember = false; }; GraphicsAPI BufferFormatter::m_API; static bool MatchBaseTypeDeclaration(QString basetype, const bool isUnsigned, ShaderConstant &el) { if(basetype == lit("bool")) { el.type.baseType = VarType::Bool; } else if(basetype == lit("byte") || basetype == lit("char") || basetype == lit("int8_t")) { el.type.baseType = VarType::SByte; if(isUnsigned) el.type.baseType = VarType::UByte; } else if(basetype == lit("ubyte") || basetype == lit("xbyte") || basetype == lit("uint8_t")) { el.type.baseType = VarType::UByte; } else if(basetype == lit("short") || basetype == lit("int16_t")) { el.type.baseType = VarType::SShort; if(isUnsigned) el.type.baseType = VarType::UShort; } else if(basetype == lit("ushort") || basetype == lit("xshort") || basetype == lit("uint16_t")) { el.type.baseType = VarType::UShort; } else if(basetype == lit("long") || basetype == lit("int64_t")) { el.type.baseType = VarType::SLong; if(isUnsigned) el.type.baseType = VarType::ULong; } else if(basetype == lit("ulong") || basetype == lit("xlong") || basetype == lit("uint64_t")) { el.type.baseType = VarType::ULong; } else if(basetype == lit("int") || basetype == lit("ivec") || basetype == lit("imat") || basetype == lit("int32_t")) { el.type.baseType = VarType::SInt; if(isUnsigned) el.type.baseType = VarType::UInt; } else if(basetype == lit("uint") || basetype == lit("xint") || basetype == lit("uvec") || basetype == lit("umat") || basetype == lit("uint32_t")) { el.type.baseType = VarType::UInt; } else if(basetype == lit("half") || basetype == lit("float16_t")) { el.type.baseType = VarType::Half; } else if(basetype == lit("float") || basetype == lit("vec") || basetype == lit("mat") || basetype == lit("float32_t")) { el.type.baseType = VarType::Float; } else if(basetype == lit("double") || basetype == lit("dvec") || basetype == lit("dmat") || basetype == lit("float64_t")) { el.type.baseType = VarType::Double; } else if(basetype == lit("unormh")) { el.type.baseType = VarType::UShort; el.type.flags |= ShaderVariableFlags::UNorm; } else if(basetype == lit("unormb")) { el.type.baseType = VarType::UByte; el.type.flags |= ShaderVariableFlags::UNorm; } else if(basetype == lit("snormh")) { el.type.baseType = VarType::SShort; el.type.flags |= ShaderVariableFlags::SNorm; } else if(basetype == lit("snormb")) { el.type.baseType = VarType::SByte; el.type.flags |= ShaderVariableFlags::SNorm; } else if(basetype == lit("uintten")) { el.type.baseType = VarType::UInt; el.type.flags |= ShaderVariableFlags::R10G10B10A2; el.type.columns = 4; } else if(basetype == lit("unormten")) { el.type.baseType = VarType::UInt; el.type.flags |= ShaderVariableFlags::R10G10B10A2; el.type.flags |= ShaderVariableFlags::UNorm; el.type.columns = 4; } else if(basetype == lit("floateleven")) { el.type.baseType = VarType::Float; el.type.flags |= ShaderVariableFlags::R11G11B10; el.type.columns = 3; } else { return false; } return true; } static QString MakeIdentifierName(const rdcstr &name) { QString ret = name; ret = ret.replace(QLatin1Char('['), QLatin1Char('_')).replace(QLatin1Char(']'), QString()); if(ret[0].isDigit()) ret.prepend(QLatin1Char('_')); ret.replace(QRegularExpression(lit("[^A-Za-z0-9@_]+")), lit("_")); return ret; } void BufferFormatter::EstimatePackingRules(Packing::Rules &pack, ResourceId shader, const ShaderConstant &constant, uint32_t knownVecAlignment) { // see if this constant violates any of the packing rules we are currently checking for. // We can't *prove* a rule is followed just from one example, we can only see if it is never // *disproved*. This does mean we won't necessarily determine the exact packing scheme, e.g if // scalar packing was used but it was only three float4 vectors then it will look like the most // conservative std140/scalar. if(!pack.vector_align_component || !pack.vector_straddle_16b) { // column major matrices have vectors that are 'rows' long. Everything else is vectors of // 'columns' long uint8_t vecSize = constant.type.columns; uint8_t matSize = constant.type.rows; if(constant.type.rows > 1 && constant.type.ColMajor()) { vecSize = constant.type.rows; matSize = constant.type.columns; } if(vecSize > 1) { // is this a vector that's only component aligned and NOT vector aligned. If so, // vector_align_component is true const uint32_t vec4Size = VarTypeByteSize(constant.type.baseType) * 4; const uint32_t offsModVec = (constant.byteOffset % vec4Size); // if it's a vec3 or vec4 and its offset is not purely aligned, it's only component aligned if(vecSize >= 3 && offsModVec != 0) pack.vector_align_component = true; // if it's a vec2 and its offset is not either 0 or half the total size, it's also only // component aligned. vec2s without this allowance must be aligned to the vec2 size if(vecSize == 2 && offsModVec != 0 && offsModVec != vec4Size / 2) pack.vector_align_component = true; if(constant.type.elements > 1) { // with arrays we can check the stride as well. If the stride isn't vector-aligned then // that's the same as vectors being aligned to components (even if we don't see it) if(vecSize >= 3 && constant.type.arrayByteStride < vec4Size) pack.vector_align_component = true; if(vecSize == 2 && constant.type.arrayByteStride < vec4Size / 2) pack.vector_align_component = true; } if(matSize > 1) { if(vecSize >= 3 && constant.type.matrixByteStride < vec4Size) pack.vector_align_component = true; if(vecSize == 2 && constant.type.matrixByteStride < vec4Size / 2) pack.vector_align_component = true; } // while we're here, check if the vector straddles a 16-byte boundary const uint32_t low16b = (constant.byteOffset / 16); const uint32_t high16b = ((constant.byteOffset + VarTypeByteSize(constant.type.baseType) * vecSize - 1) / 16); // if the vector crosses a 16-byte boundary, vectors can straddle them if(low16b != high16b) pack.vector_straddle_16b = true; // if we have determined earlier that a struct array may misalign the vector's base alignment, we are straddling if(vecSize >= 3 && knownVecAlignment < 16) pack.vector_straddle_16b = true; else if(vecSize == 2 && knownVecAlignment < 8) pack.vector_straddle_16b = true; } if(!pack.tight_arrays && matSize > 1) { // if the array has a byte stride less than 16, it must be non-tight packed if(constant.type.matrixByteStride < 16) pack.tight_arrays = true; } } if(!pack.tight_arrays && constant.type.elements > 1) { // if the array has a byte stride less than 16, it must be non-tight packed if(constant.type.arrayByteStride < 16) pack.tight_arrays = true; } if(!pack.tight_arrays && constant.type.baseType == VarType::Struct) { // if a struct isn't padded to 16-byte alignment, assume non-tight arrays if((constant.type.arrayByteStride % 16) != 0) pack.tight_arrays = true; } // handle the case where a structs array stride may need to pessimise its members' alignments. // // e.g. struct foo { float4 a; float b; } is technically naturally aligned, but if foo bar[]; has // a stride of 20 that means that bar[1].a will not be aligned anymore and will be straddling a // boundary - we must detect that if(constant.type.baseType == VarType::Struct) { uint32_t structVecAlign = constant.type.arrayByteStride % 16; if(structVecAlign == 8) knownVecAlignment = qMin(knownVecAlignment, 8U); else if(structVecAlign == 4 || structVecAlign == 12) knownVecAlignment = qMin(knownVecAlignment, 4U); else if(structVecAlign == 2 || structVecAlign == 6 || structVecAlign == 10 || structVecAlign == 14) knownVecAlignment = qMin(knownVecAlignment, 2U); else if(structVecAlign != 0) knownVecAlignment = 1; } EstimatePackingRules(pack, shader, constant.type.members, knownVecAlignment); } void BufferFormatter::EstimatePackingRules(Packing::Rules &pack, ResourceId shader, const rdcarray &members, uint32_t knownVecAlignment) { for(size_t i = 0; i < members.size(); i++) { // check this constant EstimatePackingRules(pack, shader, members[i], knownVecAlignment); // when pointers are in use, follow the type and estimate with those too if(members[i].type.pointerTypeID != ~0U) { const ShaderConstantType &ptrType = PointerTypeRegistry::GetTypeDescriptor(shader, members[i].type.pointerTypeID); EstimatePackingRules(pack, shader, ptrType.members, knownVecAlignment); } // check for trailing array/struct use if(i > 0) { Packing::Rules unpadded = pack; Packing::Rules padded = pack; unpadded.trailing_overlap = true; unpadded.vector_align_component = true; padded.trailing_overlap = false; const uint32_t unpaddedAdvance = GetVarAdvance(unpadded, members[i - 1]); const uint32_t paddedAdvance = GetVarAdvance(padded, members[i - 1]); // if we overlap into the previous element, and it contains padding, then trailing padding is // not reserved. This applies to structs, arrays and matrices. if(paddedAdvance > unpaddedAdvance && members[i].byteOffset < (members[i - 1].byteOffset + paddedAdvance)) { pack.trailing_overlap = true; } } // if we've degenerated to scalar we can't get any more lenient, stop checking rules if(pack == Packing::Scalar) break; } } Packing::Rules BufferFormatter::EstimatePackingRules(ResourceId shader, const rdcarray &members) { Packing::Rules pack; // start from the most conservative ruleset. We will iteratively turn off any rules which are // violated to end up with the most conservative ruleset which is still valid for the described // variable // D3D shouldn't really need to be estimating, because it's implicit from how this is bound // (cbuffer or structured resource) if(IsD3D(m_API)) pack = Packing::D3DCB; else pack = Packing::std140; // without more information we must assume all vectors are naturally aligned EstimatePackingRules(pack, shader, members, 16); // only return a 'real' ruleset. Don't revert to individually setting rules if we can help it // since that's a mess. The worst case is if someone is really using a custom packing format then // we add some extra offset decorations // only look for layouts typical of the API in use if(IsD3D(m_API)) { // scalar is technically more lenient than anything D3D allows, as D3DUAV requires padding after // structs (it's closer to C packing) if(pack == Packing::D3DCB || pack == Packing::D3DUAV || pack == Packing::Scalar) return pack; // shouldn't end up with these as we started at D3DCB, but just for safety if(pack == Packing::std140) return Packing::D3DCB; if(pack == Packing::std430) return Packing::D3DUAV; } else { if(pack == Packing::std140 || pack == Packing::std430 || pack == Packing::Scalar) return pack; if(m_API == GraphicsAPI::Vulkan) { if(pack == Packing::D3DCB || pack == Packing::D3DUAV) return pack; // on vulkan HLSL shaders may use relaxed block layout, which is not wholly represented here. // it doesn't actually allow trailing overlap but this lets us check if we're 'almost' cbuffer // rules, at which point any instances where trailing overlap would be used will look just // like manual padding/offsetting Packing::Rules mod = pack; mod.trailing_overlap = true; if(mod == Packing::D3DCB) return Packing::D3DCB; } } // don't explicitly use C layout, revert to scalar which is more typical in graphics // the worst case is that some elements that would be in trailing padding in structs get explicit // offset annotations to move them out, since in C that would be implicit. // // note, D3DUAV is treated the same as C but we checked for it above so we'd only get here on // non-D3D if(pack == Packing::C) return Packing::Scalar; // our ruleset doesn't match exactly to a premade one. Check the rules to see which properties we // have. // Currently this always means devolving to scalar, but we lay it out explicitly like this in case // other rulesets are added in future. // only scalar layouts allow straddling 16 byte alignment, it would be very strange to allow // straddling 16 bytes but e.g. not have tight arrays or component-aligned vectors. Possibly no // arrays were seen so tight arrays couldn't be explicitly determined. So regardless of what else // we found return scalar if(pack.vector_straddle_16b) return Packing::Scalar; // trailing overlap is allowed in any D3D layout, but for non-D3D only in scalar layout. // Since we know from above that either we're not using D3D or we aren't an exact match for D3DCB, // assume we're in scalar one way or another. // This could be e.g. D3DUAV with tight arrays but vector straddling wasn't seen explicitly if(pack.trailing_overlap) return Packing::Scalar; // the exact same logic as above applies to component-aligned vectors. Allowed in any D3D layout, // but for non-D3D only in scalar layout. if(pack.vector_align_component) return Packing::Scalar; // For non-D3D: if we have tight arrays, this is possible in std430 - however since we didn't // match std430 above there must be some other allowance. That means we must devolve to scalar // For D3D this is possible only in D3DUAV (which is equivalent to scalar) if(pack.tight_arrays) return Packing::Scalar; // shouldn't get here, but just for safety return the ruleset we derived return pack; } QString BufferFormatter::DeclarePacking(Packing::Rules pack) { if(pack == Packing::D3DCB) return lit("#pack(cbuffer)"); else if(pack == Packing::std140) return lit("#pack(std140)"); else if(pack == Packing::std430) return lit("#pack(std430)"); else if(pack == Packing::D3DUAV) // this is also C but we call it 'structured' for D3D return lit("#pack(structured)"); else if(pack == Packing::Scalar) return lit("#pack(scalar)"); // packing doesn't match a premade ruleset. Emit individual specifiers QString ret; if(pack.vector_align_component) ret += lit("#pack(vector_align_component) // vectors are aligned to their component\n"); else ret += lit("#pack(no_vector_align_component) // vectors are aligned evenly (float3 as float4)\n"); if(pack.tight_arrays) ret += lit("#pack(tight_arrays) // arrays are packed tightly\n"); else ret += lit("#pack(no_tight_arrays) // arrays are padded to 16-byte boundaries\n"); if(pack.vector_straddle_16b) ret += lit("#pack(vector_straddle_16b) // vectors can straddle 16-byte boundaries\n"); else ret += lit("#pack(no_vector_straddle_16b) // vectors cannot straddle 16-byte boundaries\n"); if(pack.trailing_overlap) ret += lit("#pack(trailing_overlap) // variables can overlap trailing padding after " "arrays/structs\n"); else ret += lit("#pack(no_trailing_overlap) // variables cannot overlap trailing padding after " "arrays/structs\n"); return ret.trimmed(); } bool BufferFormatter::ContainsUnbounded(const ShaderConstant &structType, rdcpair *found) { const rdcarray &members = structType.type.members; for(size_t i = 0; i < members.size(); i++) { if(members[i].type.elements == ~0U) { if(found) *found = {structType.name, members[i].name}; return true; } if(ContainsUnbounded(members[i], found)) return true; } return false; } bool BufferFormatter::CheckInvalidUnbounded(const StructFormatData &structData, const QMap &structelems, QMap &errors) { const ShaderConstant &def = structData.structDef; for(size_t i = 0; i < def.type.members.size(); i++) { const bool isLast = i == def.type.members.size() - 1; // if it's not the last member and it's unbounded, that's a problem! if(!isLast && def.type.members[i].type.elements == ~0U) { int line = structData.lineMemberDefs[(int)i]; errors[line] = tr("Only the last member of a struct can be an unbounded array."); return false; } // if it's not the last member, no child can have an unbounded array rdcpair unbounded; if(!isLast && ContainsUnbounded(def.type.members[i], &unbounded)) { int line = structData.lineMemberDefs[(int)i]; errors[line] = tr("Only the last member of a struct can contain an unbounded array. %1 in %2 is " "unbounded.") .arg(unbounded.second) .arg(unbounded.first); return false; } if(!CheckInvalidUnbounded(structelems[def.type.members[i].type.name], structelems, errors)) return false; } return true; } ParsedFormat BufferFormatter::ParseFormatString(const QString &formatString, uint64_t maxLen, bool cbuffer) { ParsedFormat ret; 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+)?" // matrix majorness "(?unsigned\\s+|signed\\s+)?" // allow 'signed int' or 'unsigned char' "(?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|char" // 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 "|int8_t|uint8_t" // C-style sized 8-bit types "|int16_t|uint16_t" // C-style sized 16-bit types "|int32_t|uint32_t" // C-style sized 32-bit types "|int64_t|uint64_t" // C-style sized 64-bit types "|float16_t|float32_t|float64_t" // C-style sized float types ")" // end of the type group "(?[1-9])?" // might be a vector "(?x[1-9])?" // or a matrix "(" // pointer or space "(?\\s*\\*\\s*)|" // pointer asterisk "\\s+|" // or just some whitespace "$" // or the end, if it's nameless ")" // end pointer or space "(?[A-Za-z@_][A-Za-z0-9@_]*)?" // get identifier name "(?\\s*\\[[0-9]*\\])?" // optional array dimension "(\\s*:\\s*" // optional specifier after : "(" // bitfield or semantic "(?[1-9][0-9]*)|" // bitfield packing "(?[A-Za-z_][A-Za-z0-9_]*)" // semantic to ignore ")" // end bitfield or semantic ")?" "$")); bool success = true; // remove any dos newlines QString text = formatString; text.replace(lit("\r\n"), lit("\n")); QRegularExpression annotationRegex( lit("^" // start of the line "\\[\\[" // opening [[ "(?[a-zA-Z0-9_-]+)" // annotation name "(\\((?[^)]+)\\))?" // optional parameter in ()s "\\]\\]" // closing ]] "\\s*")); QRegularExpression structDeclRegex( lit("^(struct|enum)\\s+([A-Za-z_][A-Za-z0-9_]*)(\\s*:\\s*([a-z]+))?$")); QRegularExpression structUseRegex( lit("^" // start of the line "([A-Za-z_][A-Za-z0-9_]*)" // struct type name "([ \\t\\r\\n*]+)" // maybe a pointer, but at least some whitespace "([A-Za-z@_][A-Za-z0-9@_]*)?" // variable name "(\\s*\\[[0-9]*\\])?" // optional array dimension "(\\s*:\\s*([1-9][0-9]*))?" // optional bitfield packing "$")); QRegularExpression enumValueRegex( lit("^" // start of the line "([A-Za-z_][A-Za-z0-9_]*)" // value name "\\s*=\\s*" // maybe a pointer "(-?0x[0-9a-fA-F]+|-?[0-9]+)" // numerical value "$")); QRegularExpression bitfieldSkipRegex( lit("^" // start of the line "(unsigned\\s+|signed\\s+)?" // allow 'signed int' or 'unsigned char' "(" // type group "|bool" // bool is stored as 4-byte int "|byte|short|int|long|char" // signed ints "|ubyte|ushort|uint|ulong" // unsigned ints "|xbyte|xshort|xint|xlong" // hex ints ")" // end of the type group // no variable name "\\s*:\\s*([1-9][0-9]*)" // bitfield packing "$")); QRegularExpression packingRegex( lit("^" // start of the line "#\\s*pack\\s*\\(" // #pack( "(?[a-zA-Z0-9_]+)" // packing ruleset or individual rule "\\)" // ) "$")); uint32_t bitfieldCurPos = ~0U; struct Annotation { QString name; QString param; }; // default to scalar (tight packing) if nothing else is specified at all. The expectation is // anything that needs a better default will insert that into the format string for the user, // or be picked up below Packing::Rules &pack = ret.packing; pack = Packing::Scalar; // for D3D and GL we default to the only valid packing for cbuffers and UAVs. The user can still // override this if they really wish with a #pack, but this makes sense as a sensible default if(cbuffer) { if(IsD3D(m_API)) pack = Packing::D3DCB; else if(m_API == GraphicsAPI::OpenGL) pack = Packing::std140; } else { if(IsD3D(m_API)) pack = Packing::D3DUAV; else if(m_API == GraphicsAPI::OpenGL) pack = Packing::std430; } // vulkan allows scalar packing in any buffer, so don't wrest control away from the user int line = 0; QMap &errors = ret.errors; auto reportError = [&line, &errors](QString err) { errors[line] = err.trimmed(); }; QList annotations; QString parseText = text; int parseLine = 0; // get each line and parse it to determine the format the user wanted while(!parseText.isEmpty()) { // consume up to the next terminator (comma, semicolon, brace, or newline) while ignore C and // C++ style comments, as well as counting newlines for line numbers enum parsestate { NORMAL, C_COMMENT, CPP_COMMENT } state = NORMAL; QString decl; { int end = 0; for(; end < parseText.length();) { // peek ahead character QChar c = parseText[end]; // if we have a non-empty declaration and we're about to hit a brace, stop now before // actually processing it. const bool brace = (c == QLatin1Char('{') || c == QLatin1Char('}')); if(brace && !decl.trimmed().isEmpty()) break; // consume c now, whatever it is, we've read it and will process it below end++; // if this is a ; or , we don't bother to include it in the declaration but stop now if(state == NORMAL && (c == QLatin1Char(';') || c == QLatin1Char(','))) break; if(c == QLatin1Char('\n')) { parseLine++; // if we're in a CPP comment, go back to normal if(state == CPP_COMMENT) state = NORMAL; // if we have a preprocessor definition (first non-whitespace character is #) end at the // end of the line without a ; if(state == NORMAL && decl.trimmed().startsWith(lit("#"))) break; } QChar c2; if(end + 1 < parseText.length()) c2 = parseText[end]; if(state == NORMAL && c == QLatin1Char('/') && c2 == QLatin1Char('/')) { // consume the next character too end++; state = CPP_COMMENT; continue; } if(state == NORMAL && c == QLatin1Char('/') && c2 == QLatin1Char('*')) { // consume the next character too end++; state = C_COMMENT; continue; } if(state == C_COMMENT && c == QLatin1Char('*') && c2 == QLatin1Char('/')) { // consume the next character too end++; state = NORMAL; continue; } if(state == NORMAL) { decl.append(c); line = parseLine; // braces should be considered their own declarations if(brace) break; } } parseText = parseText.mid(end); decl = decl.trimmed(); } if(decl.isEmpty()) continue; do { QRegularExpressionMatch match = annotationRegex.match(decl); if(!match.hasMatch()) break; annotations.push_back({match.captured(lit("name")), match.captured(lit("param"))}); decl.remove(match.capturedStart(0), match.capturedLength(0)); decl = decl.trimmed(); } while(true); if(decl.isEmpty()) continue; if(decl[0] == QLatin1Char('#')) { QRegularExpressionMatch match = packingRegex.match(decl); if(match.hasMatch()) { if(cur != &root) { reportError(tr("Packing rules can only be changed at global scope.")); success = false; break; } QString packrule = match.captured(lit("rule")).toLower(); // try to pick up common aliases that people might use if(packrule == lit("d3dcbuffer") || packrule == lit("cbuffer") || packrule == lit("cb")) pack = Packing::D3DCB; else if(packrule == lit("d3duav") || packrule == lit("uav") || packrule == lit("structured")) pack = Packing::D3DUAV; else if(packrule == lit("std140") || packrule == lit("ubo") || packrule == lit("gl") || packrule == lit("gles") || packrule == lit("opengl") || packrule == lit("glsl")) pack = Packing::std140; else if(packrule == lit("std430") || packrule == lit("ssbo")) pack = Packing::std430; else if(packrule == lit("scalar")) pack = Packing::Scalar; else if(packrule == lit("c")) pack = Packing::C; // we also allow toggling the individual rules else if(packrule == lit("vector_align_component")) pack.vector_align_component = true; else if(packrule == lit("no_vector_align_component")) pack.vector_align_component = false; else if(packrule == lit("tight_arrays")) pack.tight_arrays = true; else if(packrule == lit("no_tight_arrays")) pack.tight_arrays = false; else if(packrule == lit("vector_straddle_16b")) pack.vector_straddle_16b = true; else if(packrule == lit("no_vector_straddle_16b")) pack.vector_straddle_16b = false; else if(packrule == lit("trailing_overlap")) pack.trailing_overlap = true; else if(packrule == lit("no_trailing_overlap")) pack.trailing_overlap = false; else if(packrule == lit("tight_bitfield_packing")) pack.tight_bitfield_packing = true; else if(packrule == lit("no_tight_bitfield_packing")) pack.tight_bitfield_packing = false; else packrule = QString(); if(packrule.isEmpty()) { reportError(tr("Unrecognised packing rule specifier '%1'.\n\n" "Supported rulesets:\n" " - cbuffer (D3D constant buffer packing)\n" " - uav (D3D UAV packing)\n" " - std140 (GL/Vulkan std140 packing)\n" " - std430 (GL/Vulkan std430 packing)\n" " - scalar (Tight scalar packing)") .arg(packrule)); success = false; break; } continue; } else { reportError(tr("Unrecognised pre-processor command '%1'.\n\n" "Pre-processor commands must be all on one line.\n") .arg(decl)); success = false; break; } } if(cur == &root) { // if we're not in a struct, ignore the braces if(decl == lit("{") || decl == 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(decl == lit("{")) continue; if(decl == lit("}")) { if(bitfieldCurPos != ~0U) { // update final offset to account for any bits consumed by a trailing bitfield, including // any bits in the last byte that weren't allocated cur->offset += (bitfieldCurPos + 7) / 8; // reset bitpacking state. bitfieldCurPos = ~0U; } if(cur->structDef.type.baseType == VarType::Struct) { cur->structDef.type.arrayByteStride = cur->offset; if(cur->alignment == 0) cur->alignment = GetAlignment(pack, cur->structDef); // if we don't have tight arrays, struct byte strides are always 16-byte aligned if(!pack.tight_arrays) { cur->alignment = 16; } cur->structDef.type.arrayByteStride = AlignUp(cur->offset, cur->alignment); if(cur->paddedStride > 0) { // only pad up to the stride, not down if(cur->paddedStride >= cur->structDef.type.arrayByteStride) { cur->structDef.type.arrayByteStride = cur->paddedStride; } else { reportError(tr("Struct %1 declared size %2 bytes is less than derived structure " "size:\n%3 bytes with alignment %4 meaning %5 bytes total size.") .arg(cur->structDef.type.name) .arg(cur->paddedStride) .arg(cur->offset) .arg(cur->alignment) .arg(cur->structDef.type.arrayByteStride)); success = false; break; } } cur->pointerTypeId = PointerTypeRegistry::GetTypeID(cur->structDef.type); } cur = &root; continue; } } if(decl.startsWith(lit("struct")) || decl.startsWith(lit("enum"))) { QRegularExpressionMatch match = structDeclRegex.match(decl); if(match.hasMatch()) { QString typeName = match.captured(1); QString name = match.captured(2); if(structelems.contains(name)) { reportError(tr("type %1 has already been defined.").arg(name)); success = false; break; } cur = &structelems[name]; cur->structDef.type.name = name; bitfieldCurPos = ~0U; if(typeName == lit("struct")) { lastStruct = name; cur->structDef.type.baseType = VarType::Struct; for(const Annotation &annot : annotations) { if(annot.name == lit("size") || annot.name == lit("byte_size")) { if(annot.param.isEmpty()) { reportError(tr("Annotation '%1' requires a parameter with the size in bytes.\n\n" "e.g. [[%1(128)]]") .arg(annot.name)); success = false; break; } cur->paddedStride = annot.param.toUInt(); } else if(annot.name == lit("align") || annot.name == lit("alignment")) { if(annot.param.isEmpty()) { reportError(tr("Annotation '%1' requires a parameter with the size in bytes.\n\n" "e.g. [[%1(128)]]") .arg(annot.name)); success = false; break; } cur->alignment = annot.param.toUInt(); } else if(annot.name == lit("single") || annot.name == lit("fixed")) { cur->singleDef = true; } else { reportError( tr("Unrecognised annotation '%1' on struct definition.\n\n" "Supported struct annotations:\n" " - [[size(x)]] specify the size to pad the struct to.\n" " - [[single]] specify that this struct is fixed, not array-of-structs.") .arg(annot.name)); success = false; break; } } annotations.clear(); if(!success) break; } else { cur->structDef.type.baseType = VarType::Enum; for(const Annotation &annot : annotations) { if(false) { // no annotations supported currently on enums } else { reportError(tr("Unrecognised annotation '%1' on enum definition.").arg(annot.name)); success = false; break; } } annotations.clear(); if(!success) break; QString baseType = match.captured(4); if(baseType.isEmpty()) { reportError( tr("Enum declarations require a sized base type. E.g. enum %1 : uint").arg(name)); success = false; break; } ShaderConstant tmp; bool matched = MatchBaseTypeDeclaration(baseType, false, tmp); if(!matched || (VarTypeCompType(tmp.type.baseType) != CompType::UInt && VarTypeCompType(tmp.type.baseType) != CompType::SInt) || tmp.type.flags != ShaderVariableFlags::NoFlags) { reportError(tr("Invalid enum base type '%1', must be an integer type.").arg(baseType)); success = false; break; } cur->structDef.type.matrixByteStride = VarTypeByteSize(tmp.type.baseType); cur->structDef.type.flags = (VarTypeCompType(tmp.type.baseType) == CompType::SInt) ? ShaderVariableFlags::SignedEnum : ShaderVariableFlags::NoFlags; } continue; } } ShaderConstant el; if(cur->structDef.type.baseType == VarType::Enum) { QRegularExpressionMatch enumMatch = enumValueRegex.match(decl); if(!enumMatch.hasMatch()) { reportError(tr("Couldn't parse value declaration in enum.")); success = false; break; } QString valueNum = enumMatch.captured(2); bool ok = false; if(cur->structDef.type.flags & ShaderVariableFlags::SignedEnum) { int64_t val = valueNum.toLongLong(&ok, 0); if(ok) { // convert signed 'literally' to unsigned and truncate if(cur->structDef.type.matrixByteStride == 1) { if(val > INT8_MAX || val < INT8_MIN) { reportError( tr("Enum with 8-bit signed integer type cannot hold value '%1'.").arg(valueNum)); success = false; break; } int8_t truncVal = (int8_t)val; memcpy(&el.defaultValue, &truncVal, sizeof(truncVal)); } else if(cur->structDef.type.matrixByteStride == 2) { if(val > INT16_MAX || val < INT16_MIN) { reportError( tr("Enum with 16-bit signed integer type cannot hold value '%1'.").arg(valueNum)); success = false; break; } int16_t truncVal = (int16_t)val; memcpy(&el.defaultValue, &truncVal, sizeof(truncVal)); } else if(cur->structDef.type.matrixByteStride == 4) { if(val > INT32_MAX || val < INT32_MIN) { reportError( tr("Enum with 32-bit signed integer type cannot hold value '%1'.").arg(valueNum)); success = false; break; } int32_t truncVal = (int32_t)val; memcpy(&el.defaultValue, &truncVal, sizeof(truncVal)); } else if(cur->structDef.type.matrixByteStride == 8) { el.defaultValue = valueNum.toULongLong(); } } if(!ok) { reportError(tr("Couldn't parse enum numerical value from '%1'.").arg(valueNum)); success = false; break; } } else { if(valueNum[0] == QLatin1Char('-')) { reportError(tr("Enum with unsigned base type cannot have signed value.")); success = false; break; } el.defaultValue = valueNum.toULongLong(&ok, 0); if(el.defaultValue > (UINT64_MAX >> (64 - 8 * cur->structDef.type.matrixByteStride))) { reportError(tr("Enum with %1-bit signed integer type cannot hold value '%1'.") .arg(8 * cur->structDef.type.matrixByteStride) .arg(valueNum)); success = false; break; } if(!ok) { valueNum.toULongLong(&ok, 0); reportError(tr("Couldn't get enum numerical value from '%1'.").arg(valueNum)); success = false; break; } } el.name = enumMatch.captured(1); for(const Annotation &annot : annotations) { if(false) { // no annotations supported currently on enums } else { reportError(tr("Unrecognised annotation '%1' on enum value.").arg(annot.name)); success = false; break; } } annotations.clear(); if(!success) break; cur->structDef.type.members.push_back(el); cur->lineMemberDefs.push_back(line); continue; } QRegularExpressionMatch bitfieldSkipMatch = bitfieldSkipRegex.match(decl); if(bitfieldSkipMatch.hasMatch()) { if(bitfieldCurPos == ~0U) bitfieldCurPos = 0; bitfieldCurPos += bitfieldSkipMatch.captured(3).toUInt(); for(const Annotation &annot : annotations) { if(false) { // no annotations supported currently on enums } else { reportError(tr("Unrecognised annotation '%1' on bitfield skip element.").arg(annot.name)); success = false; break; } } annotations.clear(); if(!success) break; continue; } if(cur->singleMember) { reportError( tr("[[single]] can only be used if there is only one variable in the root.\n" "Consider wrapping the variables in a struct and annotating it as [[single]].")); success = false; break; } QRegularExpressionMatch structMatch = structUseRegex.match(decl); bool isPadding = false; bool isPointer = false; uint32_t specifiedOffset = ~0U; if(structMatch.hasMatch() && structelems.contains(structMatch.captured(1))) { StructFormatData &structContext = structelems[structMatch.captured(1)]; QString pointerStars = structMatch.captured(2).trimmed(); isPointer = !pointerStars.isEmpty(); if(pointerStars.count() > 1) { reportError(tr("Only single pointers are supported.")); success = false; break; } if(structContext.singleDef) { reportError(tr("[[single]] annotated structs can't be used, only defined.")); success = false; break; } if(!isPointer && structContext.structDef.type.name == cur->structDef.type.name) { reportError(tr("Invalid nested struct declaration, only allowed for pointers.")); success = false; break; } QString varName = structMatch.captured(3).trimmed(); if(varName.isEmpty()) varName = lit("data"); for(const Annotation &annot : annotations) { if(annot.name == lit("offset") || annot.name == lit("byte_offset")) { if(annot.param.isEmpty()) { reportError(tr("Annotation '%1' requires a parameter with the offset in bytes.\n\n" "e.g. [[%1(128)]]") .arg(annot.name)); success = false; break; } specifiedOffset = annot.param.toUInt(); } else if(annot.name == lit("pad") || annot.name == lit("padding")) { isPadding = true; } else if(annot.name == lit("single") || annot.name == lit("fixed")) { if(cur != &root) { reportError(tr("[[single]] can only be used on global variables.")); success = false; break; } else if(!cur->structDef.type.members.empty()) { reportError( tr("[[single]] can only be used if there is only one variable in the root.\n" "Consider wrapping the variables in a struct and marking it as [[single]].")); success = false; break; } else { cur->singleMember = true; } } else { reportError(tr("Unrecognised annotation '%1' on variable.").arg(annot.name)); success = false; break; } } if(!success) break; annotations.clear(); QString arrayDim = structMatch.captured(4).trimmed(); uint32_t arrayCount = 1; if(!arrayDim.isEmpty()) { arrayDim = arrayDim.mid(1, arrayDim.count() - 2); if(arrayDim.isEmpty()) arrayDim = lit("%1").arg(~0U); bool ok = false; arrayCount = arrayDim.toUInt(&ok); if(!ok) arrayCount = 1; } if(cur->singleMember && arrayCount == ~0U) { reportError(tr("[[single]] can't be used on unbounded arrays.")); success = false; break; } QString bitfield = structMatch.captured(6).trimmed(); if(isPointer) { if(!bitfield.isEmpty()) { reportError(tr("Pointers can't be packed into a bitfield.")); success = false; break; } // evaluate any bitfield offset if(bitfieldCurPos != ~0U) { // update final offset to account for any bits consumed by a trailing bitfield, including // any bits in the last byte that weren't allocated cur->offset += (bitfieldCurPos + 7) / 8; // reset bitpacking state. bitfieldCurPos = ~0U; } // align to scalar size cur->offset = AlignUp(cur->offset, 8U); if(specifiedOffset != ~0U) { if(specifiedOffset < cur->offset) { reportError(tr("Specified byte offset %1 overlaps with previous data.\n" "This value must be at byte offset %2 at minimum.") .arg(specifiedOffset) .arg(cur->offset)); success = false; break; } cur->offset = specifiedOffset; } el.name = varName; el.byteOffset = cur->offset; el.type.pointerTypeID = structContext.pointerTypeId; el.type.baseType = VarType::GPUPointer; el.type.flags |= ShaderVariableFlags::HexDisplay; el.type.arrayByteStride = 8; el.type.elements = arrayCount; cur->offset += 8 * arrayCount; if(!isPadding) { cur->structDef.type.members.push_back(el); cur->lineMemberDefs.push_back(line); } continue; } else if(structContext.structDef.type.baseType == VarType::Enum) { if(!bitfield.isEmpty() && !arrayDim.isEmpty()) { reportError(tr("Arrays can't be packed into a bitfield.")); success = false; break; } // align to scalar size (if not bit packing) if(bitfieldCurPos == ~0U) cur->offset = AlignUp(cur->offset, (uint32_t)structContext.structDef.type.matrixByteStride); if(specifiedOffset != ~0U) { uint32_t offs = cur->offset; if(bitfieldCurPos != ~0U) offs += (bitfieldCurPos + 7) / 8; if(specifiedOffset < offs) { reportError(tr("Specified byte offset %1 overlaps with previous data.\n" "This value must be at byte offset %2 at minimum.") .arg(specifiedOffset) .arg(offs)); success = false; break; } cur->offset = specifiedOffset; // reset any bitfield packing to start at 0 at the new location if(bitfieldCurPos != ~0U) bitfieldCurPos = 0; } el = structContext.structDef; el.name = varName; el.byteOffset = cur->offset; el.type.elements = arrayCount; bool ok = false; el.bitFieldSize = qMax(1U, bitfield.toUInt(&ok)); if(!ok) el.bitFieldSize = 0; // don't continue here - we will go through and handle bitfield packing like any other // scalar } else { if(!bitfield.isEmpty()) { reportError(tr("Struct variables can't be packed into a bitfield.")); success = false; break; } if(bitfieldCurPos != ~0U) cur->offset += (bitfieldCurPos + 7) / 8; // all packing rules align structs in the same way as arrays. We already calculated this // when calculating the struct's alignment which will be padded to 16B for non-tight arrays cur->offset = AlignUp(cur->offset, structContext.alignment); // reset any bitfield packing to start at 0 at the new location if(bitfieldCurPos != ~0U) bitfieldCurPos = 0; if(specifiedOffset != ~0U) { if(specifiedOffset < cur->offset) { reportError(tr("Specified byte offset %1 overlaps with previous data.\n" "This value must be at byte offset %2 at minimum.") .arg(specifiedOffset) .arg(cur->offset)); success = false; break; } cur->offset = specifiedOffset; } el = structContext.structDef; el.name = varName; el.byteOffset = cur->offset; el.type.elements = arrayCount; if(!isPadding) { cur->structDef.type.members.push_back(el); cur->lineMemberDefs.push_back(line); } // advance by the struct including any trailing padding cur->offset += el.type.elements * el.type.arrayByteStride; // if we allow trailing overlap, remove the padding if(pack.trailing_overlap) cur->offset -= el.type.arrayByteStride - structContext.offset; continue; } } else { QRegularExpressionMatch match = regExpr.match(decl); if(!match.hasMatch()) { QString problemGuess; // try to guess the problem since we don't have a proper parser and are just using regex's, // so we don't have a parse state to mention ShaderConstant dummy; int numRecognisedTypes = 0; QStringList identifiers = decl.split(QRegularExpression(lit("\\s+"))); for(const QString &identifier : identifiers) { bool known = MatchBaseTypeDeclaration(identifier, false, dummy); if(known) numRecognisedTypes++; } // if we recognised more than one type maybe this is multiple lines that got combined if(numRecognisedTypes > 1) { problemGuess = tr("Did you need a ; between multiple declarations?"); } else if(identifiers.size() >= 1 && structelems.contains(identifiers[0])) { problemGuess = tr("Invalid declaration of struct '%1'.").arg(identifiers[0]); } else if(identifiers.size() > 1) { problemGuess = tr("Unrecognised type '%1'.").arg(identifiers[0]); } reportError( tr("Failed to parse declaration:\n\n%1\n\n%2").arg(decl).arg(problemGuess).trimmed()); success = false; break; } el.name = !match.captured(lit("name")).isEmpty() ? match.captured(lit("name")).trimmed() : lit("data"); QString basetype = match.captured(lit("type")); if(match.captured(lit("major")).trimmed() == lit("row_major")) el.type.flags |= ShaderVariableFlags::RowMajorMatrix; if(!match.captured(lit("rgb")).isEmpty()) el.type.flags |= ShaderVariableFlags::RGBDisplay; QString firstDim = !match.captured(lit("vec")).isEmpty() ? match.captured(lit("vec")) : lit("1"); QString secondDim = !match.captured(lit("mat")).isEmpty() ? match.captured(lit("mat")).mid(1) : lit("1"); QString arrayDim = !match.captured(lit("array")).isEmpty() ? match.captured(lit("array")).trimmed() : lit("[1]"); isPointer = !match.captured(lit("ptr")).isEmpty(); { bool isArray = !arrayDim.isEmpty(); arrayDim = arrayDim.mid(1, arrayDim.count() - 2).trimmed(); if(isArray && arrayDim.isEmpty()) arrayDim = lit("%1").arg(~0U); } const bool isUnsigned = match.captured(lit("sign")).trimmed() == lit("unsigned"); QString bitfield = match.captured(lit("bitfield")); QString vecMatSizeSuffix; // if we have a matrix and it's not GL style, then typeAxB means A rows and B columns // for GL matAxB that means A columns and B rows. This is in contrast to typeA which means A // columns for HLSL and A columns for GLSL, hence only the swap for matrices if(!match.captured(lit("mat")).isEmpty() && basetype != lit("mat")) { vecMatSizeSuffix = match.captured(lit("vec")) + match.captured(lit("mat")); firstDim.swap(secondDim); } else { if(!match.captured(lit("mat")).isEmpty()) vecMatSizeSuffix = match.captured(lit("mat")).mid(1) + lit("x"); vecMatSizeSuffix += match.captured(lit("vec")); } // check for square matrix declarations like 'mat4' and 'mat3' if(basetype == lit("mat") && match.captured(lit("mat")).isEmpty()) { secondDim = firstDim; vecMatSizeSuffix = firstDim + lit("x") + firstDim; } // check for square matrix declarations like 'mat4' and 'mat3' if(basetype == lit("mat") && match.captured(lit("mat")).isEmpty()) secondDim = firstDim; // calculate format { bool ok = false; el.type.columns = firstDim.toUInt(&ok); if(!ok) { reportError(tr("Invalid vector dimension '%1'.").arg(firstDim)); success = false; break; } el.type.elements = qMax(1U, arrayDim.toUInt(&ok)); if(!ok) el.type.elements = 1; if(!bitfield.isEmpty() && el.type.elements > 1) { reportError(tr("Arrays can't be packed into a bitfield.")); success = false; break; } el.type.rows = qMax(1U, secondDim.toUInt(&ok)); if(!ok) { reportError(tr("Invalid matrix dimension '%1'.").arg(secondDim)); success = false; break; } el.bitFieldSize = qMax(1U, bitfield.toUInt(&ok)); if(!ok) el.bitFieldSize = 0; // vectors are marked as row-major by convention if(el.type.rows == 1) el.type.flags |= ShaderVariableFlags::RowMajorMatrix; bool matched = MatchBaseTypeDeclaration(basetype, isUnsigned, el); if(!matched) { reportError(tr("Unrecognised type '%1'.").arg(basetype)); success = false; break; } } el.type.name = ToStr(el.type.baseType) + vecMatSizeSuffix; // process packing annotations first, so we have that information to validate e.g. [[unorm]] for(const Annotation &annot : annotations) { if(annot.name == lit("packed")) { if(annot.param.toLower() == lit("r11g11b10")) { if(el.type.columns != 3 || el.type.baseType != VarType::Float) { reportError(tr("R11G11B10 packing must be specified on a 'float3' variable.")); success = false; break; } el.type.flags |= ShaderVariableFlags::R11G11B10; } else if(annot.param.toLower() == lit("r10g10b10a2")) { // check if there's a (legacy) separate snorm bool snorm = false; for(const Annotation &normAnnot : annotations) { if(normAnnot.name == lit("snorm")) { snorm = true; break; } } // if we've specified snorm check for signed type if(snorm) { if(el.type.columns != 4 || el.type.baseType != VarType::SInt) { reportError( tr("R10G10B10A2 packing must be specified on a 'int4' variable when used " "with [[snorm]].")); success = false; break; } } // otherwise assume unorm and enforce unsigned type else { if(el.type.columns != 4 || el.type.baseType != VarType::UInt) { reportError( tr("R10G10B10A2 packing must be specified on a 'uint4' variable " "(optionally an 'int4' with preceeding [[snorm]]).")); success = false; break; } } el.type.flags |= ShaderVariableFlags::R10G10B10A2; } else if(annot.param.toLower() == lit("r10g10b10a2_uint")) { if(el.type.columns != 4 || el.type.baseType != VarType::UInt) { reportError( tr("R10G10B10A2 packing must be specified on a 'uint4' variable " "(optionally with [[unorm]] or [[snorm]]).")); success = false; break; } el.type.flags |= ShaderVariableFlags::R10G10B10A2; } else if(annot.param.toLower() == lit("r10g10b10a2_unorm")) { if(el.type.columns != 4 || el.type.baseType != VarType::UInt) { reportError(tr("R10G10B10A2_UNORM packing must be specified on a 'uint4' variable.")); success = false; break; } el.type.flags |= ShaderVariableFlags::R10G10B10A2 | ShaderVariableFlags::UNorm; } else if(annot.param.toLower() == lit("r10g10b10a2_snorm")) { if(el.type.columns != 4 || el.type.baseType != VarType::SInt) { reportError(tr("R10G10B10A2_SNORM packing must be specified on a 'int4' variable.")); success = false; break; } el.type.flags |= ShaderVariableFlags::R10G10B10A2 | ShaderVariableFlags::SNorm; } else if(annot.param.isEmpty()) { reportError(tr("Annotation '%1' requires a parameter with the format packing.\n\n" "e.g. [[%1(r10g10b10a2)]]") .arg(annot.name)); success = false; break; } else { reportError(tr("Unrecognised format packing '%1'.\n").arg(annot.param)); success = false; break; } } } if(!success) break; for(const Annotation &annot : annotations) { if(annot.name == lit("rgb")) { el.type.flags |= ShaderVariableFlags::RGBDisplay; } else if(annot.name == lit("hex") || annot.name == lit("hexadecimal")) { if(VarTypeCompType(el.type.baseType) == CompType::Float) { reportError(tr("Hex display is not supported on floating point variables.")); success = false; break; } if(el.type.flags & (ShaderVariableFlags::R10G10B10A2 | ShaderVariableFlags::R11G11B10)) { reportError(tr("Hex display is not supported on packed formats.")); success = false; break; } el.type.flags |= ShaderVariableFlags::HexDisplay; if(el.type.baseType == VarType::SLong) el.type.baseType = VarType::ULong; else if(el.type.baseType == VarType::SInt) el.type.baseType = VarType::UInt; else if(el.type.baseType == VarType::SShort) el.type.baseType = VarType::UShort; else if(el.type.baseType == VarType::SByte) el.type.baseType = VarType::UByte; } else if(annot.name == lit("bin") || annot.name == lit("binary")) { if(VarTypeCompType(el.type.baseType) == CompType::Float) { reportError(tr("Binary display is not supported on floating point variables.")); success = false; break; } if(el.type.flags & (ShaderVariableFlags::R10G10B10A2 | ShaderVariableFlags::R11G11B10)) { reportError(tr("Binary display is not supported on packed formats.")); success = false; break; } el.type.flags |= ShaderVariableFlags::BinaryDisplay; if(el.type.baseType == VarType::SLong) el.type.baseType = VarType::ULong; else if(el.type.baseType == VarType::SInt) el.type.baseType = VarType::UInt; else if(el.type.baseType == VarType::SShort) el.type.baseType = VarType::UShort; else if(el.type.baseType == VarType::SByte) el.type.baseType = VarType::UByte; } else if(annot.name == lit("unorm")) { if(!(el.type.flags & ShaderVariableFlags::R10G10B10A2)) { // verify that we're integer typed and 1 or 2 bytes if(el.type.baseType != VarType::UShort && el.type.baseType != VarType::SShort && el.type.baseType != VarType::UByte && el.type.baseType != VarType::SByte) { reportError(tr("UNORM packing is only supported on [u]byte and [u]short types.")); success = false; break; } } el.type.flags |= ShaderVariableFlags::UNorm; } else if(annot.name == lit("snorm")) { if(!(el.type.flags & ShaderVariableFlags::R10G10B10A2)) { // verify that we're integer typed and 1 or 2 bytes if(el.type.baseType != VarType::UShort && el.type.baseType != VarType::SShort && el.type.baseType != VarType::UByte && el.type.baseType != VarType::SByte) { reportError(tr("SNORM packing is only supported on [u]byte and [u]short types.")); success = false; break; } } el.type.flags |= ShaderVariableFlags::SNorm; } else if(annot.name == lit("row_major")) { if(el.type.rows == 1) { reportError(tr("Row major can only be specified on matrices.")); success = false; break; } el.type.flags |= ShaderVariableFlags::RowMajorMatrix; } else if(annot.name == lit("col_major")) { if(el.type.rows == 1) { reportError(tr("Column major can only be specified on matrices.")); success = false; break; } el.type.flags &= ~ShaderVariableFlags::RowMajorMatrix; } else if(annot.name == lit("packed")) { // already processed } else if(annot.name == lit("offset") || annot.name == lit("byte_offset")) { if(annot.param.isEmpty()) { reportError(tr("Annotation '%1' requires a parameter with the offset in bytes.\n\n" "e.g. [[%1(128)]]") .arg(annot.name)); success = false; break; } specifiedOffset = annot.param.toUInt(); } else if(annot.name == lit("pad") || annot.name == lit("padding")) { isPadding = true; } else if(annot.name == lit("single") || annot.name == lit("fixed")) { if(cur != &root) { reportError(tr("[[single]] can only be used on global variables.")); success = false; break; } else if(!cur->structDef.type.members.empty()) { reportError( tr("[[single]] can only be used if there is only one variable in the root.\n" "Consider wrapping the variables in a struct and marking it as [[single]].")); success = false; break; } else { cur->singleMember = true; } } else { reportError(tr("Unrecognised annotation '%1' on variable.").arg(annot.name)); success = false; break; } } annotations.clear(); if(!success) break; // validate that bitfields are only allowed for regular scalars if(el.bitFieldSize > 0) { if(el.type.rows > 1 || el.type.columns > 1) { reportError(tr("Vectors and matrices can't be packed into a bitfield.")); success = false; break; } if(el.type.elements > 1) { reportError(tr("Arrays can't be packed into a bitfield.")); success = false; break; } if(isPointer) { reportError(tr("Pointers can't be packed into a bitfield.")); success = false; break; } if(el.type.flags & (ShaderVariableFlags::R10G10B10A2 | ShaderVariableFlags::R11G11B10 | ShaderVariableFlags::UNorm | ShaderVariableFlags::SNorm)) { reportError(tr("Format-packed variables can't be packed into a bitfield.")); success = false; break; } if(VarTypeCompType(el.type.baseType) == CompType::Float) { reportError(tr("Floating point variables can't be packed into a bitfield.")); success = false; break; } } if(basetype == lit("xlong") || basetype == lit("xint") || basetype == lit("xshort") || basetype == lit("xbyte")) el.type.flags |= ShaderVariableFlags::HexDisplay; } if(cur->singleMember && el.type.elements == ~0U) { reportError(tr("[[single]] can't be used on unbounded arrays.")); success = false; break; } const bool packed32bit = bool(el.type.flags & (ShaderVariableFlags::R10G10B10A2 | ShaderVariableFlags::R11G11B10)); // normally the array stride is the size of an element uint32_t elAlignment = packed32bit ? sizeof(uint32_t) : GetAlignment(pack, el); const uint8_t vecSize = (el.type.rows > 1 && el.type.ColMajor()) ? el.type.rows : el.type.columns; const uint32_t elSize = packed32bit ? sizeof(uint32_t) : (pack.vector_align_component ? elAlignment * vecSize : elAlignment); // if we aren't using tight arrays the stride is at least 16 bytes el.type.arrayByteStride = elAlignment; if(el.type.rows > 1 || el.type.columns > 1) el.type.arrayByteStride = elSize; if(!pack.tight_arrays) el.type.arrayByteStride = std::max(16U, el.type.arrayByteStride); // matrices are always aligned like arrays of vectors if(el.type.rows > 1) { // the alignment calculated above is the alignment of a vector, that's our matrix stride el.type.matrixByteStride = el.type.arrayByteStride; // the array stride is that alignment times the number of rows/columns if(el.type.RowMajor()) el.type.arrayByteStride *= el.type.rows; else el.type.arrayByteStride *= el.type.columns; } if(isPointer) { ShaderConstant innerConst; innerConst.type = el.type; // array is consumed by the pointer, not the inner type innerConst.type.elements = 1; innerConst.name = el.name; ShaderConstantType inner; inner.name = ""; inner.members.push_back(innerConst); el.type.pointerTypeID = PointerTypeRegistry::GetTypeID(inner); el.type.rows = 1; el.type.columns = 1; el.type.baseType = VarType::GPUPointer; el.type.flags = ShaderVariableFlags::HexDisplay; el.type.arrayByteStride = elAlignment = 8; if(!pack.tight_arrays) el.type.arrayByteStride = std::max(16U, el.type.arrayByteStride); el.type.matrixByteStride = el.type.arrayByteStride; } if(el.bitFieldSize > 0) { // we can use the elAlignment since this is a scalar so no vector/arrays, this is just the // base size. It also works for enums as this is the byte size of the declared underlying type const uint32_t elemScalarBitSize = elAlignment * 8; // bitfields can't be larger than the base type if(el.bitFieldSize > elemScalarBitSize) { reportError(tr("Variable type %1 only has %2 bits, can't pack into %3 bits in a bitfield.") .arg(el.type.name) .arg(elemScalarBitSize) .arg(el.bitFieldSize)); success = false; break; } uint32_t start = bitfieldCurPos; if(start == ~0U) start = 0; // if we would end past the current base type size, first roll over and start at the next // byte // this could be: // unsigned int a : 24; // unsigned byte b : 4; // unsigned byte c : 4; // where we just 'rollover' the 3 bytes packed into the unsigned int and start the byte on // the next byte, there's no extra padding added // or it could be: // unsigned int a : 29; // unsigned byte b : 4; // unsigned byte c : 4; // where b would pass through the end of the fourth byte so there ends up being 3 bits of // padding between a and b when b is rolled onto the next byte // similarly this can happen if the types are the same: // unsigned int a : 29; // unsigned int b : 4; // since b would still pass through the end of the first dword. // similarly this allows 'more' padding when the types are bigger: // unsigned int a : 17; // unsigned int b : 17; // which would produce 15 bytes of padding // Note that if the types are the same and big enough we won't roll over, as in: // unsigned int a : 24; // unsigned int b : 4; // unsigned int c : 4; if(start + el.bitFieldSize > elemScalarBitSize) { if(pack.tight_bitfield_packing) { while(bitfieldCurPos >= 8) { bitfieldCurPos -= 8; cur->offset++; } } else { // align the offset up to where this bitfield needs to start cur->offset += ((bitfieldCurPos + (elemScalarBitSize - 1)) / elemScalarBitSize) * (elemScalarBitSize / 8); // reset the current bitfield pos bitfieldCurPos = 0; } } // if there's no previous bitpacking, nothing much to do if(bitfieldCurPos == ~0U) { // start the next bitfield at our size bitfieldCurPos = el.bitFieldSize; } else { // start the next bitfield at the end of the previous el.bitFieldOffset = bitfieldCurPos; // update by our size bitfieldCurPos += el.bitFieldSize; } } else { // this element is not bitpacked if(bitfieldCurPos != ~0U) { // update offset to account for any bits consumed by the previous bitfield, which won't have // happened yet, including any bits in the last byte that weren't allocated cur->offset += (bitfieldCurPos + 7) / 8; // reset bitpacking state. bitfieldCurPos = ~0U; } // align to our element's base alignment cur->offset = AlignUp(cur->offset, elAlignment); // if we have non-tight arrays, arrays (and matrices) always start on a 16-byte boundary if(!pack.tight_arrays && (el.type.elements > 1 || el.type.rows > 1)) cur->offset = AlignUp(cur->offset, 16U); // if vectors can't straddle 16-byte alignment, check to see if we're going to do that if(!pack.vector_straddle_16b) { if(cur->offset / 16 != (cur->offset + elSize - 1) / 16) { cur->offset = AlignUp(cur->offset, 16U); } } } if(specifiedOffset != ~0U) { if(specifiedOffset < cur->offset) { reportError(tr("Specified byte offset %1 overlaps with previous data.\n" "This value must be at byte offset %2 at minimum.") .arg(specifiedOffset) .arg(cur->offset)); success = false; break; } // if we're bitfield packing and we just specified an offset based padding, reset current position if(bitfieldCurPos != ~0U && specifiedOffset > cur->offset) { el.bitFieldOffset = bitfieldCurPos = 0; bitfieldCurPos += el.bitFieldSize; } cur->offset = specifiedOffset; } el.byteOffset = cur->offset; if(!isPadding) { cur->structDef.type.members.push_back(el); cur->lineMemberDefs.push_back(line); } // if we're bitfield packing don't advance offset, otherwise advance to the end of this element if(bitfieldCurPos == ~0U) cur->offset += GetVarAdvance(pack, el); } if(bitfieldCurPos != ~0U) { // update final offset to account for any bits consumed by a trailing bitfield, including any // bits in the last byte that weren't allocated cur->offset += (bitfieldCurPos + 7) / 8; // reset bitpacking state. bitfieldCurPos = ~0U; } ShaderConstant &fixed = ret.fixed; // 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]; // only pad up to the stride, not down if(root.paddedStride >= root.offset) root.offset = cur->paddedStride; } fixed = root.structDef; uint32_t end = root.offset; if(!fixed.type.members.isEmpty() && (!pack.tight_bitfield_packing || fixed.type.members.back().bitFieldSize == 0)) end = qMax( end, fixed.type.members.back().byteOffset + GetVarSizeAndTrail(fixed.type.members.back())); if(root.alignment != 0) fixed.type.arrayByteStride = AlignUp(end, root.alignment); else fixed.type.arrayByteStride = AlignUp(end, GetAlignment(pack, fixed)); if(!fixed.type.members.isEmpty() && fixed.type.members.back().type.elements == ~0U) { fixed.type.arrayByteStride = AlignUp(fixed.type.members.back().type.arrayByteStride, GetAlignment(pack, fixed)); } if(success) { // check that unbounded arrays are only the last member of each struct. Doing this separately // makes the below check easier since we only have to consider last members if(!CheckInvalidUnbounded(root, structelems, errors)) success = false; } // we only allow one 'infinite' array. You can't have an infinite member inside an already // infinite struct. E.g. this is invalid: // // struct foo { // uint a; // float b[]; // }; // // foo data[]; // // but it's valid to have either this: // // struct foo { // uint a; // float b[3]; // }; // // foo data[]; // // or this: // // struct foo { // uint a; // float b[]; // }; // // foo data; if(success) { ShaderConstant *iter = &fixed; ShaderConstant *parent = NULL; bool foundInfinite = false; int infiniteArrayLine = -1; while(iter) { if(iter->type.elements == ~0U) { if(foundInfinite) { QString parentName; if(parent) parentName = parent->type.name; success = false; errors[infiniteArrayLine] = tr("Can't declare an unbounded array when child member %1 of " "struct %2 is also declared as unbounded.") .arg(iter->name) .arg(parentName); break; } foundInfinite = true; if(parent && parent != &fixed) infiniteArrayLine = structelems[parent->type.name].lineMemberDefs.back(); else infiniteArrayLine = root.lineMemberDefs.back(); } // if there are no more members, stop looking if(iter->type.members.empty()) break; parent = iter; iter = &iter->type.members.back(); } } // on D3D if we have an unbounded array it *must* be the root element, as D3D does not support // some fixed elements before it, structured buffers are strictly just an AoS. // we do allow specifying cbuffers which are all fixed and not unbounded, so we just check to see // that if there is an unbounded array that it's the root if( // on D3D IsD3D(m_API) && // if the parsing worked success && !fixed.type.members.empty() && // if we have an unbounded array somewhere (we know there's only one, from above) ContainsUnbounded(fixed) && // it must be in the root and it must be alone with no siblings !(fixed.type.members.size() == 1 && fixed.type.members[0].type.elements == ~0U)) { errors[root.lineMemberDefs.back()] = tr("On D3D an unbounded array must be only be used alone as the root element.\n" "Consider wrapping all the globals in a single struct, or removing the unbounded array " "declaration."); success = false; } // when not viewing a cbuffer, if the root hasn't been explicitly marked as a single struct and we // don't have an unbounded array then consider it an AoS definition in all other cases as that is // very likely what the user expects if(success && !fixed.type.members.empty() && !ContainsUnbounded(fixed) && !root.singleMember && !root.singleDef && !cbuffer) { // if there's already only one root member just make it infinite if(fixed.type.members.size() == 1) { fixed.type.members[0].type.elements = ~0U; } else { // otherwise wrap a struct around the members, to be the infinite AoS rdcarray inners; inners.swap(fixed.type.members); ShaderConstant el; el.byteOffset = 0; el.type.baseType = VarType::Struct; el.type.elements = ~0U; el.type.arrayByteStride = fixed.type.arrayByteStride; fixed.type.members.push_back(el); inners.swap(fixed.type.members[0].type.members); } } if(!success || fixed.type.members.isEmpty()) { fixed.type.members.clear(); fixed.type.baseType = VarType::Struct; ShaderConstant el; el.byteOffset = 0; el.type.flags = ShaderVariableFlags::RowMajorMatrix | ShaderVariableFlags::HexDisplay; el.name = "data"; el.type.name = "uint4"; el.type.baseType = VarType::UInt; el.type.columns = 4; el.type.elements = ~0U; if(maxLen > 0 && maxLen < 16) el.type.columns = 1; if(maxLen > 0 && maxLen < 4) el.type.baseType = VarType::UByte; el.type.arrayByteStride = el.type.columns * VarTypeByteSize(el.type.baseType); fixed.type.members.push_back(el); fixed.type.arrayByteStride = el.type.arrayByteStride; } // split the struct definition we have now into fixed and repeating. We've enforced above that // there's only one struct which is unbounded (no other children at any level are unbounded) and // it's the last member, so we find it, remove it from the hierarchy, and present it separately. { ShaderConstant *iter = &fixed; rdcstr addedprefix; while(iter) { // if there are no more members, stop looking, there's no repeated member if(iter->type.members.empty()) break; // add the prefix, so that the repeated element that's a child like buffer { foo { blah[] } } // shows up as buffer.foo.blah if(!iter->name.empty()) addedprefix += iter->name + "."; // we want to search the members so we can remove from the current iter if(iter->type.members.back().type.elements == ~0U) { ret.repeating = iter->type.members.back(); ret.repeating.name = addedprefix + ret.repeating.name; ret.repeating.type.elements = 1; iter->type.members.pop_back(); break; } iter = &iter->type.members.back(); } } return ret; } 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("[[unorm]] ubyte"); else if(tex.format.compType == CompType::SNorm) baseType = lit("[[snorm]] byte"); 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("[[unorm]] ushort"); else if(tex.format.compType == CompType::SNorm) baseType = lit("[[snorm]] short"); 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("[[hex]] int2"); 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("[[hex]] int4"); break; case ResourceFormatType::R10G10B10A2: baseType = lit("[[packed(r10g10b10a2)]] "); if(tex.format.compType == CompType::UNorm) baseType += lit("[[unorm]] "); baseType += lit("uint4"); break; case ResourceFormatType::R11G11B10: baseType = lit("[[rgb]] [[packed(r11g11b10)]] float3"); break; case ResourceFormatType::R5G6B5: case ResourceFormatType::R5G5B5A1: baseType = lit("[[hex]] short"); break; case ResourceFormatType::R9G9B9E5: baseType = lit("[[hex]] int"); break; case ResourceFormatType::R4G4B4A4: baseType = lit("[[hex]] short"); break; case ResourceFormatType::R4G4: baseType = lit("[[hex]] byte"); break; case ResourceFormatType::D16S8: case ResourceFormatType::D24S8: case ResourceFormatType::D32S8: case ResourceFormatType::YUV8: baseType = lit("[[hex]] byte4"); break; case ResourceFormatType::YUV10: case ResourceFormatType::YUV12: case ResourceFormatType::YUV16: baseType = lit("[[hex]] short4"); break; case ResourceFormatType::A8: case ResourceFormatType::S8: case ResourceFormatType::Undefined: baseType = lit("[[hex]] byte"); break; } if(tex.type == TextureType::Buffer) return QFormatStr("%1 %2;").arg(baseType).arg(varName); return lit("#pack(scalar) // texture formats are tightly packed\n\n" "struct row\n{\n %1 %2[%3];\n};\n\nrow r[];") .arg(baseType) .arg(varName) .arg(w); } QString BufferFormatter::GetBufferFormatString(Packing::Rules pack, ResourceId shader, const ShaderResource &res, const ResourceFormat &viewFormat) { QString format; if(!res.variableType.members.empty()) { QString structName = res.variableType.name; if(structName.isEmpty()) structName = lit("el"); QList declaredStructs; QMap anonStructs; format = DeclareStruct(pack, shader, declaredStructs, anonStructs, structName, res.variableType.members, 0, QString()); format = QFormatStr("%1\n\n%2").arg(DeclarePacking(pack)).arg(format); } else { const ShaderConstantType &desc = res.variableType; if(viewFormat.type == ResourceFormatType::Undefined || viewFormat.compType == CompType::Typeless) { if(desc.baseType == VarType::Unknown) { format = desc.name; } else { if(desc.RowMajor() && desc.rows > 1 && desc.columns > 1) format += lit("[[row_major]] "); format += ToQStr(desc.baseType); 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("[[packed(r10g10b10a2)]] uint4"); if(viewFormat.compType == CompType::UNorm) format = lit("[[packed(r10g10b10a2)]] [[unorm]] uint4"); } else if(viewFormat.type == ResourceFormatType::R11G11B10) { format = lit("[[packed(r11g11b10)]] float3"); } else { switch(viewFormat.compByteWidth) { case 1: { if(viewFormat.compType == CompType::UNorm || viewFormat.compType == CompType::UNormSRGB) format = lit("[[unorm]] ubyte"); if(viewFormat.compType == CompType::SNorm) format = lit("[[snorm]] byte"); 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("[[unorm]] ushort"); if(viewFormat.compType == CompType::SNorm) format = lit("[[snorm]] short"); 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::GetVarStraddleSize(const ShaderConstant &var) { if(var.type.baseType == VarType::Enum) return var.type.matrixByteStride; // structs don't themselves have a straddle size // this is fine because the struct members itself don't straddle, and the alignment of the max of // their members. A struct that contains a vector will have to satisfy that vector's alignment. on // std140/430 this means the struct will be aligned such that as long as its members don't // straddle then any aligned placement of the struct they also won't straddle. // for D3D cbuffers where vectors have float alignment, structs are aligned to 16 always. // all others are scalar so straddling is allowed - i.e there is no packing scheme that disallows // straddling but allows vector AND struct placement so freely that a vector member could avoid // straddling until the struct is placed at a particular offset. if(!var.type.members.empty()) return 0; if(var.type.rows > 1) return var.type.matrixByteStride; return VarTypeByteSize(var.type.baseType) * var.type.columns; } uint32_t BufferFormatter::GetVarSizeAndTrail(const ShaderConstant &var) { if(var.type.elements > 1 && var.type.elements != ~0U) return var.type.arrayByteStride * var.type.elements; if(var.type.baseType == VarType::Enum) return var.type.matrixByteStride; if(!var.type.members.empty()) return var.type.arrayByteStride; if(var.type.rows > 1) { if(var.type.RowMajor()) return var.type.matrixByteStride * var.type.rows; else return var.type.matrixByteStride * var.type.columns; } // special packed formats that only consume one dword if(var.type.flags & ShaderVariableFlags::R10G10B10A2) return 4; if(var.type.flags & ShaderVariableFlags::R11G11B10) return 4; return VarTypeByteSize(var.type.baseType) * var.type.columns; } uint32_t BufferFormatter::GetVarAdvance(const Packing::Rules &pack, const ShaderConstant &var) { uint32_t ret = GetVarSizeAndTrail(var); // if we allow trailing overlap, remove the padding at the end of the struct/array if(pack.trailing_overlap) { if(var.type.baseType == VarType::Struct) { ret -= (var.type.arrayByteStride - GetUnpaddedStructAdvance(pack, var.type.members)); } else if((var.type.elements > 1 || var.type.rows > 1) && !pack.tight_arrays) { uint8_t vecSize = var.type.columns; if(var.type.rows > 1 && var.type.ColMajor()) vecSize = var.type.rows; uint32_t elSize = GetAlignment(pack, var); if(pack.vector_align_component) elSize *= vecSize; // the padding is the stride (which is rounded up to 16 for non-tight arrays) minus the size // of the last vector (whether or not this is an array of scalars, vectors or matrices ret -= 16 - elSize; } } return ret; } uint32_t BufferFormatter::GetAlignment(Packing::Rules pack, const ShaderConstant &c) { uint32_t ret = 1; if(c.type.baseType == VarType::Struct) { for(const ShaderConstant &m : c.type.members) ret = std::max(ret, GetAlignment(pack, m)); } else if(c.type.baseType == VarType::Enum) { ret = c.type.matrixByteStride; } else if(c.type.members.empty()) { uint32_t align = VarTypeByteSize(c.type.baseType); // if vectors aren't component aligned we need to calculate the alignment based on the size of // the vectors if(!pack.vector_align_component) { // column major matrices have vectors that are 'rows' long. Everything else is vectors of // 'columns' long uint8_t vecSize = c.type.columns; if(c.type.rows > 1 && c.type.ColMajor()) vecSize = c.type.rows; // 3- and 4- vectors are 4-component aligned if(vecSize >= 3) align *= 4; // 2- vectors are 2-component aligned else if(vecSize == 2) align *= 2; } ret = std::max(ret, align); } return ret; } uint32_t BufferFormatter::GetUnpaddedStructAdvance(Packing::Rules pack, const rdcarray &members) { uint32_t lastMemberStart = 0; if(members.empty()) return 0; const ShaderConstant *lastChild = &members.back(); lastMemberStart += lastChild->byteOffset; while(lastChild->type.baseType != VarType::Enum && !lastChild->type.members.isEmpty()) { if(lastChild->type.elements != ~0U) lastMemberStart += (qMax(lastChild->type.elements, 1U) - 1) * lastChild->type.arrayByteStride; lastChild = &lastChild->type.members.back(); lastMemberStart += lastChild->byteOffset; } return lastMemberStart + GetVarAdvance(pack, *lastChild); } QString BufferFormatter::DeclareStruct(Packing::Rules pack, ResourceId shader, QList &declaredStructs, QMap &anonStructs, const QString &name, const rdcarray &members, uint32_t requiredByteStride, QString innerSkippedPrefixString) { QString declarations; QString ret; // don't declare outer struct for scalar-wrapped structs (generated by 'float *foo' type declarations) if(!name.isEmpty() || members.size() != 1) ret = lit("struct %1\n{\n").arg(MakeIdentifierName(name)); ret += innerSkippedPrefixString; uint32_t offset = 0; uint32_t bitfieldOffset = 0; uint32_t bitfieldAdvance = 0; uint32_t structAlignment = 1; for(int i = 0; i < members.count(); i++) { const uint32_t alignment = GetAlignment(pack, members[i]); const uint32_t vecsize = GetVarStraddleSize(members[i]); structAlignment = std::max(structAlignment, alignment); // resolve any bitfield here before calculating offset padding if(members[i].bitFieldSize == 0 && bitfieldOffset > 0) { uint32_t bytesUsed = bitfieldOffset / 8; // align to the advance (the underlying type's size, e.g. uint or ulong) bytesUsed = AlignUp(bytesUsed, bitfieldAdvance); offset += bytesUsed; bitfieldOffset = bitfieldAdvance = 0; } offset = AlignUp(offset, alignment); // if things can't straddle 16-byte boundaries, check that and enforce if(!pack.vector_straddle_16b) { if(offset / 16 != (offset + vecsize - 1) / 16) offset = AlignUp(offset, 16U); } // if we don't have tight arrays, arrays and structs begin at 16-byte boundaries if(!pack.tight_arrays && (members[i].type.baseType == VarType::Struct || members[i].type.elements > 1 || members[i].type.rows > 1)) { offset = AlignUp(offset, 16U); } // if we're bitfield packing, collate all bits together (with padding as needed) without // updating offset yet and allow 'overlaps' at the byte offset level if(members[i].bitFieldSize != 0) { // declare empty bits if needed if(bitfieldOffset < members[i].bitFieldOffset) { ret += lit(" uint : %1;\n").arg(members[i].bitFieldOffset - bitfieldOffset); } bitfieldOffset = members[i].bitFieldOffset; bitfieldOffset += members[i].bitFieldSize; bitfieldAdvance = qMax(bitfieldAdvance, GetVarAdvance(pack, members[i])); } else { // if this variable is placed later, add an offset annotation if(offset < members[i].byteOffset) ret += lit(" [[offset(%1)]]\n").arg(members[i].byteOffset); else if(offset > members[i].byteOffset) qCritical() << "Unexpected offset overlap at" << QString(members[i].name) << "in" << QString(name); offset = members[i].byteOffset; offset += GetVarAdvance(pack, members[i]); } QString arraySize; if(members[i].type.elements > 1) { if(members[i].type.elements != ~0U) arraySize = QFormatStr("[%1]").arg(members[i].type.elements); else arraySize = lit("[]"); } QString varTypeName = MakeIdentifierName(members[i].type.name); if(members[i].type.pointerTypeID != ~0U) { const ShaderConstantType &pointeeType = PointerTypeRegistry::GetTypeDescriptor(shader, members[i].type.pointerTypeID); // don't declare pointer structs for plain scalar pointers (float *foo type) if(pointeeType.name.empty() && pointeeType.members.size() == 1) { varTypeName = pointeeType.members[0].type.name; } else { varTypeName = MakeIdentifierName(pointeeType.name); if(!declaredStructs.contains(varTypeName)) { declaredStructs.push_back(varTypeName); declarations += DeclareStruct(pack, shader, declaredStructs, anonStructs, varTypeName, pointeeType.members, pointeeType.arrayByteStride, QString()) + lit("\n"); } } varTypeName += lit("*"); } else if(members[i].type.baseType == VarType::Enum) { if(!declaredStructs.contains(varTypeName)) { declaredStructs.push_back(varTypeName); const bool signedEnum = bool(members[i].type.flags & ShaderVariableFlags::SignedEnum); VarType enumType = signedEnum ? VarType::SInt : VarType::UInt; if(members[i].type.arrayByteStride == 1) enumType = signedEnum ? VarType::SByte : VarType::UByte; else if(members[i].type.arrayByteStride == 2) enumType = signedEnum ? VarType::SShort : VarType::UShort; else if(members[i].type.arrayByteStride == 8) enumType = signedEnum ? VarType::SLong : VarType::ULong; declarations += DeclareEnum(varTypeName, members[i].type.members, enumType) + lit("\n"); } } else if(members[i].type.baseType == VarType::Struct) { // 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")) { ShaderConstant key; key.type.members = members[i].type.members; if(anonStructs.contains(key)) { varTypeName = anonStructs[key]; } else { varTypeName = anonStructs[key] = lit("struct%1").arg(anonStructs.size() + 1); } } if(!declaredStructs.contains(varTypeName)) { declaredStructs.push_back(varTypeName); declarations += DeclareStruct(pack, shader, declaredStructs, anonStructs, varTypeName, members[i].type.members, members[i].type.arrayByteStride, QString()) + lit("\n"); } } QString varName = MakeIdentifierName(members[i].name); if(varName.isEmpty()) varName = QFormatStr("_child%1").arg(i); if(members[i].type.flags & ShaderVariableFlags::RGBDisplay) varTypeName = lit("[[rgb]] ") + varTypeName; if(members[i].type.flags & ShaderVariableFlags::SNorm) varTypeName = lit("[[snorm]] ") + varTypeName; if(members[i].type.flags & ShaderVariableFlags::UNorm) varTypeName = lit("[[unorm]] ") + varTypeName; if(members[i].type.flags & ShaderVariableFlags::R10G10B10A2) varTypeName = lit("[[packed(r10g10b10a2)]] ") + varTypeName; if(members[i].type.flags & ShaderVariableFlags::R11G11B10) varTypeName = lit("[[packed(r11g11b10)]] ") + varTypeName; // don't print the [[hex]] for pointer types if(members[i].type.pointerTypeID == ~0U && (members[i].type.flags & ShaderVariableFlags::HexDisplay)) varTypeName = lit("[[hex]] ") + varTypeName; if(members[i].type.flags & ShaderVariableFlags::BinaryDisplay) varTypeName = lit("[[bin]] ") + varTypeName; if(members[i].type.rows > 1) { if(members[i].type.RowMajor()) varTypeName = lit("[[row_major]] ") + varTypeName; uint32_t stride = GetAlignment(pack, members[i]); if(pack.vector_align_component) { if(members[i].type.RowMajor()) stride *= members[i].type.columns; else stride *= members[i].type.rows; } if(!pack.tight_arrays) stride = 16; if(stride != members[i].type.matrixByteStride) ret += lit("// unexpected matrix stride %1").arg(members[i].type.matrixByteStride); } QString bitfieldSize; if(members[i].bitFieldSize > 0) bitfieldSize = QFormatStr(" : %1").arg(members[i].bitFieldSize); // don't declare outer struct for scalar-wrapped structs (generated by 'float *foo' type declarations) if(!name.isEmpty() || members.size() != 1) ret += lit(" "); ret += QFormatStr("%1 %2%3%4;\n").arg(varTypeName).arg(varName).arg(arraySize).arg(bitfieldSize); } // if we don't have tight arrays, struct byte strides are always 16-byte aligned if(!pack.tight_arrays) { structAlignment = 16; } offset = AlignUp(offset, structAlignment); if(requiredByteStride > 0) { if(requiredByteStride > offset) ret = lit("[[size(%1)]]\n%2").arg(requiredByteStride).arg(ret); else if(requiredByteStride != offset) ret = lit("// Unexpected size of struct %1\n%2").arg(requiredByteStride).arg(ret); } // don't declare outer struct for scalar-wrapped structs (generated by 'float *foo' type declarations) if(!name.isEmpty() || members.size() != 1) ret += lit("}\n"); return declarations + ret; } QString BufferFormatter::DeclareEnum(const QString &name, const rdcarray &members, VarType baseType) { QString ret; const bool signedEnum = VarTypeCompType(baseType) == CompType::SInt; ShaderVariable tmp; tmp.columns = 1; ret += QFormatStr("enum %1 : %2\n{\n").arg(name).arg(ToQStr(baseType)); for(const ShaderConstant &c : members) { tmp.value.u64v[0] = c.defaultValue; ret += QFormatStr(" %1 = %2,\n").arg(c.name).arg(RowString(tmp, 0, baseType)); } ret += lit("}\n"); return ret; } QString BufferFormatter::DeclareStruct(Packing::Rules pack, ResourceId shader, const QString &name, const rdcarray &members, uint32_t requiredByteStride) { QList declaredStructs; QMap anonStructs; QString structDef = DeclareStruct(pack, shader, declaredStructs, anonStructs, name, members, requiredByteStride, QString()); return QFormatStr("%1\n\n%2").arg(DeclarePacking(pack)).arg(structDef); } ResourceFormat GetInterpretedResourceFormat(const ShaderConstant &elem) { ResourceFormat format; format.type = ResourceFormatType::Regular; if(elem.type.flags & ShaderVariableFlags::R10G10B10A2) format.type = ResourceFormatType::R10G10B10A2; else if(elem.type.flags & ShaderVariableFlags::R11G11B10) format.type = ResourceFormatType::R11G11B10; format.compType = VarTypeCompType(elem.type.baseType); if(elem.type.flags & ShaderVariableFlags::UNorm) format.compType = CompType::UNorm; else if(elem.type.flags & ShaderVariableFlags::SNorm) format.compType = CompType::SNorm; format.compByteWidth = VarTypeByteSize(elem.type.baseType); if(elem.type.baseType == VarType::Enum) format.compByteWidth = elem.type.matrixByteStride; if(elem.type.RowMajor() || elem.type.rows == 1) format.compCount = elem.type.columns; else format.compCount = elem.type.rows; if(elem.type.baseType == VarType::GPUPointer) { format.compCount = 1; format.compType = CompType::UInt; format.compByteWidth = 8; } return format; } static void FillShaderVarData(ShaderVariable &var, const ShaderConstant &elem, const byte *data, const byte *end) { int src = 0; uint32_t outerCount = elem.type.rows; uint32_t innerCount = elem.type.columns; bool colMajor = false; if(elem.type.ColMajor() && outerCount > 1) { colMajor = true; std::swap(outerCount, innerCount); } QVariantList objs = GetVariants(GetInterpretedResourceFormat(elem), elem, data, end); if(objs.isEmpty()) { var.name = elem.name; var.value = ShaderValue(); var.flags |= ShaderVariableFlags::Truncated; return; } for(uint32_t outer = 0; outer < outerCount; outer++) { for(uint32_t inner = 0; inner < innerCount; inner++) { uint32_t dst = outer * innerCount + inner; 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::Enum: var.value.u64v[dst] = o.value().val; break; case VarType::GPUPointer: var.SetTypedPointer(o.toULongLong(), ResourceId(), elem.type.pointerTypeID); 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.baseType; ret.columns = qMin(elem.type.columns, uint8_t(4)); ret.rows = qMin(elem.type.rows, uint8_t(4)); ret.flags = elem.type.flags; if(elem.type.baseType != VarType::Enum && !elem.type.members.isEmpty()) { ret.rows = ret.columns = 0; if(elem.type.elements > 1 && elem.type.elements != ~0U) { rdcarray arrayElements; for(uint32_t a = 0; a < elem.type.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.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.baseType == VarType::Struct && elem.type.members.isEmpty()) { } else if(elem.type.elements > 1 && elem.type.elements != ~0U) { rdcarray arrayElements; for(uint32_t a = 0; a < elem.type.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.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 ShaderConstant &var, const byte *&data, const byte *end) { const ShaderConstantType &varType = var.type; 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 = varType.rows; uint32_t colCount = varType.columns; for(uint32_t row = 0; row < qMax(rowCount, 1U); row++) { for(uint32_t col = 0; col < qMax(colCount, 1U); col++) { if(varType.RowMajor() || rowCount == 1) data = base + row * varType.matrixByteStride + col * format.compByteWidth; else data = base + col * varType.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(var.bitFieldSize == 0) { 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 { uint64_t uval = 0; if(format.compByteWidth == 8) uval = readObj(data, end, ok); else if(format.compByteWidth == 4) uval = readObj(data, end, ok); else if(format.compByteWidth == 2) uval = readObj(data, end, ok); else if(format.compByteWidth == 1) uval = readObj(data, end, ok); int64_t val = 0; if(ok) { // shift by the offset uval >>= var.bitFieldOffset; // mask by the size const uint64_t mask = ((1ULL << var.bitFieldSize) - 1ULL); uval &= mask; // sign extend by hand if(uval & (1ULL << (var.bitFieldSize - 1))) uval |= ~0ULL ^ mask; memcpy(&val, &uval, sizeof(uval)); } if(format.compByteWidth == 8) ret.push_back(qlonglong(val)); else if(format.compByteWidth == 4) ret.push_back(int32_t(val)); else if(format.compByteWidth == 2) ret.push_back(int16_t(val)); else if(format.compByteWidth == 1) ret.push_back(int8_t(val)); } } else if(format.compType == CompType::UInt) { if(var.bitFieldSize == 0) { 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 { uint64_t val = 0; if(format.compByteWidth == 8) val = readObj(data, end, ok); else if(format.compByteWidth == 4) val = readObj(data, end, ok); else if(format.compByteWidth == 2) val = readObj(data, end, ok); else if(format.compByteWidth == 1) val = readObj(data, end, ok); if(ok) { // shift by the offset val >>= var.bitFieldOffset; // mask by the size val &= ((1ULL << var.bitFieldSize) - 1ULL); } else { val = 0; } if(format.compByteWidth == 8) ret.push_back(qulonglong(val)); else if(format.compByteWidth == 4) ret.push_back(uint32_t(val)); else if(format.compByteWidth == 2) ret.push_back(uint16_t(val)); else if(format.compByteWidth == 1) ret.push_back(uint8_t(val)); } if(var.type.baseType == VarType::Enum && !ret.empty()) { EnumInterpValue newval; newval.val = ret.back().toULongLong(); for(size_t i = 0; i < var.type.members.size(); i++) { if(newval.val == var.type.members[i].defaultValue) { newval.str = var.type.members[i].name; break; } } if(newval.str.isEmpty()) newval.str = QApplication::translate("BufferFormatter", "Unknown %1 (%2)") .arg(QString(var.type.name)) .arg(newval.val); ret.back() = QVariant::fromValue(newval); } } 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, const ShaderConstant &c) { if(!v.members.isEmpty() || v.type == VarType::Struct || v.type == VarType::Enum) { if(v.type == VarType::Struct || v.type == VarType::Enum) { QString structName = v.type == VarType::Struct ? lit("struct") : lit("enum"); if(c.type.baseType == v.type && !c.type.name.empty()) structName = c.type.name; if(!v.members.empty() && v.members[0].name.contains('[')) return lit("%1[%2]").arg(structName).arg(v.members.count()); else return lit("%1").arg(structName); } else { return QFormatStr("%1[%2]") .arg(TypeString(v.members[0], c.type.members.empty() ? c : c.type.members[0])) .arg(v.members.count()); } } if(v.type == VarType::GPUPointer) { const ShaderConstantType &typeDescriptor = PointerTypeRegistry::GetTypeDescriptor(v.GetPointer()); // for scalar-wrapped structs (generated by 'float *foo' type declarations) use the inner typename if(typeDescriptor.name.empty() && typeDescriptor.members.size() == 1) return typeDescriptor.members[0].type.name + "*"; return typeDescriptor.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.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) { if(flags & ShaderVariableFlags::Truncated) { QString ret = lit("---"); for(int i = 1; i < cols; i++) ret += lit(", ---"); return ret; } const bool hex = bool(flags & ShaderVariableFlags::HexDisplay); if(bool(flags & ShaderVariableFlags::BinaryDisplay)) { if(cols == 1) return Formatter::BinFormat(x); else if(cols == 2) return QFormatStr("%1, %2").arg(Formatter::BinFormat(x)).arg(Formatter::BinFormat(y)); else if(cols == 3) return QFormatStr("%1, %2, %3") .arg(Formatter::BinFormat(x)) .arg(Formatter::BinFormat(y)) .arg(Formatter::BinFormat(z)); else return QFormatStr("%1, %2, %3, %4") .arg(Formatter::BinFormat(x)) .arg(Formatter::BinFormat(y)) .arg(Formatter::BinFormat(z)) .arg(Formatter::BinFormat(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(v.type == VarType::Struct) return lit("{ ... }"); if(!v.members.empty()) 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::Enum: 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, const ShaderConstant &c) { if(!v.members.isEmpty()) return QString(); if(v.type == VarType::Enum) { uint64_t val = v.value.u64v[0]; for(size_t i = 0; i < c.type.members.size(); i++) if(val == c.type.members[i].defaultValue) return c.type.members[i].name; return QApplication::translate("BufferFormatter", "Unknown %1 (%2)") .arg(QString(c.type.name)) .arg(val); } 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()).name + "*"; QString typeStr = ToQStr(v.type); if(v.columns == 1) return typeStr; return QFormatStr("%1%2").arg(typeStr).arg(v.columns); } #if ENABLE_UNIT_TESTS #include "3rdparty/catch/catch.hpp" TEST_CASE("Round-trip struct declarations", "[formatter]") { BufferFormatter::Init(GraphicsAPI::Vulkan); QString fmt; // things not tested here: // comments, spacing, etc for obvious reasons // [[pad]] because it's eliminated entirely and will not round-trip // packing rules, they are tested below // aliased typenames like float32_t or int16_t fmt = lit(R"(#pack(structured) struct ptr_struct { [[snorm]] byte4 a; [[unorm]] byte4 a; [[packed(r11g11b10)]] float3 b; [[packed(r10g10b10a2)]] uint4 c; } struct nested { float x[4]; [[hex]] uint y; [[bin]] uint z; [[row_major]] float3x4 w; [[rgb]] float4 col; [[offset(128)]] int* p; ptr_struct* q; } enum MyEnum : uint { A = 1, B = 2, C = 10, D = 10, } struct root { float a; int b; uint c; int d; double e; short f; byte g; float3 h; float2x2 i; int3x4 j; uint k : 5; uint l : 6; uint : 3; uint m : 7; nested n; MyEnum o; } )"); ParsedFormat parsed = BufferFormatter::ParseFormatString(fmt, 0, true); CHECK(parsed.errors.isEmpty()); QString regenerated = BufferFormatter::DeclareStruct(parsed.packing, ResourceId(), parsed.fixed.type.name, parsed.fixed.type.members, parsed.fixed.type.arrayByteStride); CHECK((fmt == regenerated)); } TEST_CASE("Packing rules respected when round-tripping", "[formatter]") { BufferFormatter::Init(GraphicsAPI::Vulkan); ShaderResource res; ResourceFormat fmt; rdcarray &members = res.variableType.members; ParsedFormat parsed; members.push_back({}); members.back().name = "a"; members.back().byteOffset = 0; members.back().type.name = "float"; members.back().type.flags = ShaderVariableFlags::RowMajorMatrix; members.back().type.baseType = VarType::Float; members.back().type.arrayByteStride = 16; members.back().type.elements = 7; // std140 packing parsed = BufferFormatter::ParseFormatString( BufferFormatter::GetBufferFormatString( BufferFormatter::EstimatePackingRules(ResourceId(), members), ResourceId(), res, fmt), 0, true); CHECK((parsed.fixed.type.members == members)); // std430 packing members.back().type.arrayByteStride = 4; parsed = BufferFormatter::ParseFormatString( BufferFormatter::GetBufferFormatString( BufferFormatter::EstimatePackingRules(ResourceId(), members), ResourceId(), res, fmt), 0, true); CHECK((parsed.fixed.type.members == members)); // scalar packing members.back().type.name = "float3"; members.back().type.columns = 3; members.back().type.arrayByteStride = 12; parsed = BufferFormatter::ParseFormatString( BufferFormatter::GetBufferFormatString( BufferFormatter::EstimatePackingRules(ResourceId(), members), ResourceId(), res, fmt), 0, true); CHECK((parsed.fixed.type.members == members)); } TEST_CASE("Packing rule estimation", "[formatter]") { BufferFormatter::Init(GraphicsAPI::Vulkan); // test that each possible "rule violation" of a stricter packing ruleset bumps it down to the // appropriate new ruleset, using vulkan for the biggest range. ShaderResource res; rdcarray &members = res.variableType.members; { // use a string parse to quickly initialise our template that we'll then fiddle with QString tmpFormat = lit(R"( #pack(std140) float a; float2 a; float3 a; float4 a; float a[4]; float2 a[4]; float3 a[4]; float4 a[4]; [[col_major]] float2x4 a; [[col_major]] float4x2 a; [[row_major]] float2x4 a; [[row_major]] float4x2 a; [[col_major]] float3x4 a; [[col_major]] float4x3 a; [[row_major]] float3x4 a; [[row_major]] float4x3 a; [[col_major]] float3x4 a[4]; [[col_major]] float4x3 a[4]; [[row_major]] float3x4 a[4]; [[row_major]] float4x3 a[4]; struct struct_with_trailing { float4 inner; float4 inner; float4 inner; float3 inner; }; struct_with_trailing a; float trail_test; float3 a[4]; float trail_test; float3x4 a; float trail_test; struct struct_misaligned_by_array { float4 a; float b; // will have padding such that a is aligned in next array element }; struct outer_struct { float4 a; struct_misaligned_by_array b; }; struct outer_struct2 { float4 a; outer_struct b; }; struct_misaligned_by_array a[4]; outer_struct2 a[4]; )"); ParsedFormat tmp = BufferFormatter::ParseFormatString(tmpFormat, 1024 * 1024, true); members.swap(tmp.fixed.type.members); // space everything very conservatively to ensure we don't have to worry about overlaps uint32_t byteOffset = 0; for(size_t i = 0; i < members.size(); i++) { members[i].byteOffset = byteOffset; byteOffset += 1024; if(members[i].name == "trail_test") members[i].byteOffset = members[i - 1].byteOffset + 64; } } ResourceFormat fmt; ParsedFormat parsed; Packing::Rules pack; SECTION("No changes") { // we generated the members with std140 packing so it should stay std140 pack = BufferFormatter::EstimatePackingRules(ResourceId(), members); CHECK((pack == Packing::std140)); } SECTION("std140 compatible offsets") { for(size_t i = 0; i < members.size(); i++) { if(i == 0) // float members[i].byteOffset += 4; else if(i == 1) // float2 members[i].byteOffset += 8; else members[i].byteOffset += 16; } pack = BufferFormatter::EstimatePackingRules(ResourceId(), members); CHECK((pack == Packing::std140)); } // no other changes we can make that are std140 compatible, alignments and strides are already at // their most conservative SECTION("std430 compatible arrays") { // check that each individual change is detected as std430, to prevent one change from hiding // the lack of detection of others SECTION("float[4] tight array") { members[4].type.arrayByteStride = 4; pack = BufferFormatter::EstimatePackingRules(ResourceId(), members); CHECK((pack == Packing::std430)); } SECTION("float2[4] tight array") { members[5].type.arrayByteStride = 8; pack = BufferFormatter::EstimatePackingRules(ResourceId(), members); CHECK((pack == Packing::std430)); } SECTION("[[col_major]] float2x4 tight array") { members[8].type.matrixByteStride = 8; pack = BufferFormatter::EstimatePackingRules(ResourceId(), members); CHECK((pack == Packing::std430)); } SECTION("[[row_major]] float4x2 tight array") { members[11].type.matrixByteStride = 8; pack = BufferFormatter::EstimatePackingRules(ResourceId(), members); CHECK((pack == Packing::std430)); } } // D3DCB, on vulkan relaxed block layout, allows 'misaligned' vectors as long as they don't // straddle a 16-byte boundary SECTION("D3DCB offsets") { SECTION("float2 4-byte offset") { members[1].byteOffset += 4; pack = BufferFormatter::EstimatePackingRules(ResourceId(), members); CHECK((pack == Packing::D3DCB)); } SECTION("float3 4-byte offset") { members[2].byteOffset += 4; pack = BufferFormatter::EstimatePackingRules(ResourceId(), members); CHECK((pack == Packing::D3DCB)); } } // it becomes a scalar offset once it straddles SECTION("scalar offsets") { SECTION("float2 12-byte offset") { members[1].byteOffset += 12; pack = BufferFormatter::EstimatePackingRules(ResourceId(), members); CHECK((pack == Packing::Scalar)); } SECTION("float3 8-byte offset") { members[2].byteOffset += 8; pack = BufferFormatter::EstimatePackingRules(ResourceId(), members); CHECK((pack == Packing::Scalar)); } } SECTION("scalar array strides") { // float3[4] is the only stride of a pure array that actually changes members[6].type.arrayByteStride = 12; pack = BufferFormatter::EstimatePackingRules(ResourceId(), members); CHECK((pack == Packing::Scalar)); } SECTION("scalar matrix strides") { SECTION("[[col_major]] float3x4 tight matrix") { members[12].type.matrixByteStride = 12; pack = BufferFormatter::EstimatePackingRules(ResourceId(), members); CHECK((pack == Packing::Scalar)); } SECTION("[[row_major]] float4x3 tight matrix") { members[15].type.matrixByteStride = 12; pack = BufferFormatter::EstimatePackingRules(ResourceId(), members); CHECK((pack == Packing::Scalar)); } } SECTION("trailing struct overlap") { members[21].byteOffset = members[20].byteOffset + 64 - 4; pack = BufferFormatter::EstimatePackingRules(ResourceId(), members); CHECK((pack == Packing::Scalar)); } SECTION("trailing array overlap") { members[23].byteOffset = members[22].byteOffset + 64 - 4; pack = BufferFormatter::EstimatePackingRules(ResourceId(), members); CHECK((pack == Packing::Scalar)); } SECTION("trailing matrix overlap") { members[25].byteOffset = members[24].byteOffset + 64 - 4; pack = BufferFormatter::EstimatePackingRules(ResourceId(), members); CHECK((pack == Packing::Scalar)); } SECTION("struct vector member misaligned by array stride") { members[26].type.arrayByteStride = 20; pack = BufferFormatter::EstimatePackingRules(ResourceId(), members); CHECK((pack == Packing::Scalar)); } SECTION("nested struct vector member misaligned by array stride") { members[27].type.arrayByteStride = 52; pack = BufferFormatter::EstimatePackingRules(ResourceId(), members); CHECK((pack == Packing::Scalar)); } } TEST_CASE("Buffer format parsing", "[formatter]") { ShaderConstantType float_type; float_type.name = "float"; float_type.flags = ShaderVariableFlags::RowMajorMatrix; float_type.baseType = VarType::Float; float_type.arrayByteStride = 4; ShaderConstantType int_type; int_type.name = "int"; int_type.flags = ShaderVariableFlags::RowMajorMatrix; int_type.baseType = VarType::SInt; int_type.arrayByteStride = 4; ShaderConstantType uint_type; uint_type.name = "uint"; uint_type.flags = ShaderVariableFlags::RowMajorMatrix; uint_type.baseType = VarType::UInt; uint_type.arrayByteStride = 4; ParsedFormat parsed; BufferFormatter::Init(GraphicsAPI::Vulkan); SECTION("comment, newline, semi-colon and whitespace handling") { rdcarray members; members.push_back({}); members.back().name = "a"; members.back().byteOffset = 0; members.back().type = float_type; members.push_back({}); members.back().name = "b"; members.back().byteOffset = 4; members.back().type = int_type; members.push_back({}); members.back().name = "c"; members.back().byteOffset = 8; members.back().type = uint_type; rdcarray tests = { "float a;\n" "int b;\n" "uint c;\n", "float a;\n" "int \t \t b ;\n" "uint \n c;\n", "float a;int b;uint c;", "float a;int b ; uint c;", "// comment\n" "float a;\n" "int b;\n" "uint c;\n", "// commented declaration int x;\n" "float a;\n" "int b;\n" "uint c;\n", "/* comment */\n" "float a;\n" "int b;\n" "uint c;\n", "/* comment \n" " over multiple \n" " lines \n" "*/\n" "float a;\n" "int b;\n" "uint c;\n", "/* commented declaration int x; */\n" "float a;\n" "int b;\n" "uint c;\n", "/* comment \n" " // nested */ float a;" "int b;\n" "uint c;\n", "/* comment \n" " /* with /* extra /* opens */ float a;" "int b;\n" "uint c;\n", "float /* comment in decl */ a /* comment 2 */;\n" "int // comment in decl \n" "b // comment\n" ";\n" "uint c;\n", }; for(const rdcstr &test : tests) { parsed = BufferFormatter::ParseFormatString(test, 0, true); CHECK(parsed.errors.isEmpty()); CHECK(parsed.repeating.type.members.empty()); CHECK((parsed.fixed.type.members == members)); } } SECTION("blank format") { ShaderConstantType expected_type = uint_type; expected_type.name = "uint4"; expected_type.flags = ShaderVariableFlags::RowMajorMatrix | ShaderVariableFlags::HexDisplay; expected_type.columns = 4; expected_type.arrayByteStride = 16; parsed = BufferFormatter::ParseFormatString(QString(), 0, false); CHECK(parsed.errors.isEmpty()); CHECK((parsed.repeating.type == expected_type)); }; SECTION("Basic formats") { parsed = BufferFormatter::ParseFormatString(lit("float a;"), 0, true); CHECK(parsed.errors.isEmpty()); CHECK(parsed.repeating.type.members.empty()); REQUIRE(parsed.fixed.type.members.size() == 1); CHECK(parsed.fixed.type.members[0].name == "a"); CHECK((parsed.fixed.type.members[0].type == float_type)); parsed = BufferFormatter::ParseFormatString(lit("int a;"), 0, true); CHECK(parsed.errors.isEmpty()); CHECK(parsed.repeating.type.members.empty()); REQUIRE(parsed.fixed.type.members.size() == 1); CHECK(parsed.fixed.type.members[0].name == "a"); CHECK((parsed.fixed.type.members[0].type == int_type)); parsed = BufferFormatter::ParseFormatString(lit("uint a;"), 0, true); CHECK(parsed.errors.isEmpty()); CHECK(parsed.repeating.type.members.empty()); REQUIRE(parsed.fixed.type.members.size() == 1); CHECK(parsed.fixed.type.members[0].name == "a"); CHECK((parsed.fixed.type.members[0].type == uint_type)); rdcarray> tests = { {"half", VarType::Half}, {"double", VarType::Double}, {"ubyte", VarType::UByte}, {"byte", VarType::SByte}, {"ushort", VarType::UShort}, {"short", VarType::SShort}, {"ulong", VarType::ULong}, {"long", VarType::SLong}, {"bool", VarType::Bool}, }; for(const rdcpair &test : tests) { ShaderConstantType expect_type; expect_type.name = test.first; expect_type.flags = ShaderVariableFlags::RowMajorMatrix; expect_type.baseType = test.second; expect_type.arrayByteStride = VarTypeByteSize(test.second); parsed = BufferFormatter::ParseFormatString(lit("%1 a;").arg(test.first), 0, true); CHECK(parsed.errors.isEmpty()); CHECK(parsed.repeating.type.members.empty()); REQUIRE(parsed.fixed.type.members.size() == 1); CHECK(parsed.fixed.type.members[0].name == "a"); CHECK((parsed.fixed.type.members[0].type == expect_type)); } { ShaderConstantType expect_type; expect_type.name = "byte"; expect_type.flags = ShaderVariableFlags::RowMajorMatrix; expect_type.baseType = VarType::SByte; expect_type.arrayByteStride = 1; parsed = BufferFormatter::ParseFormatString(lit("char a;"), 0, true); CHECK(parsed.errors.isEmpty()); CHECK(parsed.repeating.type.members.empty()); REQUIRE(parsed.fixed.type.members.size() == 1); CHECK(parsed.fixed.type.members[0].name == "a"); CHECK((parsed.fixed.type.members[0].type == expect_type)); expect_type.name = "ubyte"; expect_type.baseType = VarType::UByte; parsed = BufferFormatter::ParseFormatString(lit("unsigned char a;"), 0, true); CHECK(parsed.errors.isEmpty()); CHECK(parsed.repeating.type.members.empty()); REQUIRE(parsed.fixed.type.members.size() == 1); CHECK(parsed.fixed.type.members[0].name == "a"); CHECK((parsed.fixed.type.members[0].type == expect_type)); } }; SECTION("variable-less quick formats") { parsed = BufferFormatter::ParseFormatString(lit("float"), 0, true); CHECK(parsed.errors.isEmpty()); CHECK(parsed.repeating.type.members.empty()); REQUIRE(parsed.fixed.type.members.size() == 1); CHECK((parsed.fixed.type.members[0].type == float_type)); parsed = BufferFormatter::ParseFormatString(lit("int4"), 0, true); CHECK(parsed.errors.isEmpty()); CHECK(parsed.repeating.type.members.empty()); REQUIRE(parsed.fixed.type.members.size() == 1); CHECK(parsed.fixed.type.members[0].type.baseType == VarType::SInt); CHECK(parsed.fixed.type.members[0].type.columns == 4); }; SECTION("C-style sized formats") { parsed = BufferFormatter::ParseFormatString(lit("float32_t a;"), 0, true); CHECK(parsed.errors.isEmpty()); CHECK(parsed.repeating.type.members.empty()); REQUIRE(parsed.fixed.type.members.size() == 1); CHECK(parsed.fixed.type.members[0].name == "a"); CHECK((parsed.fixed.type.members[0].type == float_type)); parsed = BufferFormatter::ParseFormatString(lit("int32_t a;"), 0, true); CHECK(parsed.errors.isEmpty()); CHECK(parsed.repeating.type.members.empty()); REQUIRE(parsed.fixed.type.members.size() == 1); CHECK(parsed.fixed.type.members[0].name == "a"); CHECK((parsed.fixed.type.members[0].type == int_type)); parsed = BufferFormatter::ParseFormatString(lit("uint32_t a;"), 0, true); CHECK(parsed.errors.isEmpty()); CHECK(parsed.repeating.type.members.empty()); REQUIRE(parsed.fixed.type.members.size() == 1); CHECK(parsed.fixed.type.members[0].name == "a"); CHECK((parsed.fixed.type.members[0].type == uint_type)); rdcarray> tests = { {"float16_t", VarType::Half}, {"float64_t", VarType::Double}, {"uint8_t", VarType::UByte}, {"int8_t", VarType::SByte}, {"uint16_t", VarType::UShort}, {"int16_t", VarType::SShort}, {"uint64_t", VarType::ULong}, {"int64_t", VarType::SLong}, }; for(uint32_t vecSize = 0; vecSize <= 4; vecSize++) { for(const rdcpair &test : tests) { ShaderConstantType expect_type; expect_type.flags = ShaderVariableFlags::RowMajorMatrix; expect_type.baseType = test.second; expect_type.columns = qMax(1U, vecSize); expect_type.arrayByteStride = VarTypeByteSize(test.second) * expect_type.columns; expect_type.name = ToStr(test.second); QString format; if(vecSize == 0) { format = lit("%1 a;").arg(test.first); } else { format = lit("%1%2 a;").arg(test.first).arg(vecSize); expect_type.name += ToStr(vecSize); } parsed = BufferFormatter::ParseFormatString(format, 0, true); CHECK(parsed.errors.isEmpty()); CHECK(parsed.repeating.type.members.empty()); REQUIRE(parsed.fixed.type.members.size() == 1); CHECK(parsed.fixed.type.members[0].name == "a"); CHECK((parsed.fixed.type.members[0].type == expect_type)); } } { ShaderConstantType expect_type; expect_type.name = "byte"; expect_type.flags = ShaderVariableFlags::RowMajorMatrix; expect_type.baseType = VarType::SByte; expect_type.arrayByteStride = 1; parsed = BufferFormatter::ParseFormatString(lit("char a;"), 0, true); CHECK(parsed.errors.isEmpty()); CHECK(parsed.repeating.type.members.empty()); REQUIRE(parsed.fixed.type.members.size() == 1); CHECK(parsed.fixed.type.members[0].name == "a"); CHECK((parsed.fixed.type.members[0].type == expect_type)); expect_type.name = "ubyte"; expect_type.baseType = VarType::UByte; parsed = BufferFormatter::ParseFormatString(lit("unsigned char a;"), 0, true); CHECK(parsed.errors.isEmpty()); CHECK(parsed.repeating.type.members.empty()); REQUIRE(parsed.fixed.type.members.size() == 1); CHECK(parsed.fixed.type.members[0].name == "a"); CHECK((parsed.fixed.type.members[0].type == expect_type)); } }; SECTION("Identifier names") { parsed = BufferFormatter::ParseFormatString(lit("int abcdef;"), 0, true); CHECK(parsed.errors.isEmpty()); REQUIRE(parsed.fixed.type.members.size() == 1); CHECK(parsed.fixed.type.members[0].name == "abcdef"); parsed = BufferFormatter::ParseFormatString(lit("int abc_def;"), 0, true); CHECK(parsed.errors.isEmpty()); REQUIRE(parsed.fixed.type.members.size() == 1); CHECK(parsed.fixed.type.members[0].name == "abc_def"); parsed = BufferFormatter::ParseFormatString(lit("int __abcdef;"), 0, true); CHECK(parsed.errors.isEmpty()); REQUIRE(parsed.fixed.type.members.size() == 1); CHECK(parsed.fixed.type.members[0].name == "__abcdef"); rdcstr longname = R"(abcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyz)"; parsed = BufferFormatter::ParseFormatString(lit("int %1;").arg(longname), 0, true); CHECK(parsed.errors.isEmpty()); REQUIRE(parsed.fixed.type.members.size() == 1); CHECK(parsed.fixed.type.members[0].name == longname); }; SECTION("Vector and matrix types") { ShaderConstantType expected_type; parsed = BufferFormatter::ParseFormatString(lit("float2 a;"), 0, true); expected_type = float_type; expected_type.name = "float2"; expected_type.flags |= ShaderVariableFlags::RowMajorMatrix; expected_type.columns = 2; expected_type.arrayByteStride = 8; CHECK(parsed.errors.isEmpty()); REQUIRE(parsed.fixed.type.members.size() == 1); CHECK((parsed.fixed.type.members[0].type == expected_type)); parsed = BufferFormatter::ParseFormatString(lit("float3 a;"), 0, true); expected_type = float_type; expected_type.name = "float3"; expected_type.flags |= ShaderVariableFlags::RowMajorMatrix; expected_type.columns = 3; expected_type.arrayByteStride = 12; CHECK(parsed.errors.isEmpty()); REQUIRE(parsed.fixed.type.members.size() == 1); CHECK((parsed.fixed.type.members[0].type == expected_type)); parsed = BufferFormatter::ParseFormatString(lit("float4 a;"), 0, true); expected_type = float_type; expected_type.name = "float4"; expected_type.flags |= ShaderVariableFlags::RowMajorMatrix; expected_type.columns = 4; expected_type.arrayByteStride = 16; CHECK(parsed.errors.isEmpty()); REQUIRE(parsed.fixed.type.members.size() == 1); CHECK((parsed.fixed.type.members[0].type == expected_type)); parsed = BufferFormatter::ParseFormatString(lit("float3x2 a;"), 0, true); expected_type = float_type; expected_type.name = "float3x2"; expected_type.flags = ShaderVariableFlags::NoFlags; expected_type.rows = 3; expected_type.columns = 2; // these are calculated with scalar packing for now, packing rules are tested separately expected_type.arrayByteStride = 24; expected_type.matrixByteStride = 12; CHECK(parsed.errors.isEmpty()); REQUIRE(parsed.fixed.type.members.size() == 1); CHECK((parsed.fixed.type.members[0].type == expected_type)); parsed = BufferFormatter::ParseFormatString(lit("float2x3 a;"), 0, true); expected_type = float_type; expected_type.name = "float2x3"; expected_type.flags = ShaderVariableFlags::NoFlags; expected_type.rows = 2; expected_type.columns = 3; expected_type.arrayByteStride = 24; expected_type.matrixByteStride = 8; CHECK(parsed.errors.isEmpty()); REQUIRE(parsed.fixed.type.members.size() == 1); CHECK((parsed.fixed.type.members[0].type == expected_type)); }; SECTION("GL style vector and matrix types") { ShaderConstantType expected_type; parsed = BufferFormatter::ParseFormatString(lit("vec2 a;"), 0, true); expected_type = float_type; expected_type.name = "float2"; expected_type.flags |= ShaderVariableFlags::RowMajorMatrix; expected_type.columns = 2; expected_type.arrayByteStride = 8; CHECK(parsed.errors.isEmpty()); REQUIRE(parsed.fixed.type.members.size() == 1); CHECK((parsed.fixed.type.members[0].type == expected_type)); parsed = BufferFormatter::ParseFormatString(lit("vec3 a;"), 0, true); expected_type = float_type; expected_type.name = "float3"; expected_type.flags |= ShaderVariableFlags::RowMajorMatrix; expected_type.columns = 3; expected_type.arrayByteStride = 12; CHECK(parsed.errors.isEmpty()); REQUIRE(parsed.fixed.type.members.size() == 1); CHECK((parsed.fixed.type.members[0].type == expected_type)); parsed = BufferFormatter::ParseFormatString(lit("vec4 a;"), 0, true); expected_type = float_type; expected_type.name = "float4"; expected_type.flags |= ShaderVariableFlags::RowMajorMatrix; expected_type.columns = 4; expected_type.arrayByteStride = 16; CHECK(parsed.errors.isEmpty()); REQUIRE(parsed.fixed.type.members.size() == 1); CHECK((parsed.fixed.type.members[0].type == expected_type)); parsed = BufferFormatter::ParseFormatString(lit("mat3x2 a;"), 0, true); expected_type = float_type; expected_type.name = "float2x3"; expected_type.flags = ShaderVariableFlags::NoFlags; expected_type.rows = 2; expected_type.columns = 3; // these are calculated with scalar packing for now, packing rules are tested separately expected_type.arrayByteStride = 24; expected_type.matrixByteStride = 8; CHECK(parsed.errors.isEmpty()); REQUIRE(parsed.fixed.type.members.size() == 1); CHECK((parsed.fixed.type.members[0].type == expected_type)); parsed = BufferFormatter::ParseFormatString(lit("mat2x3 a;"), 0, true); expected_type = float_type; expected_type.name = "float3x2"; expected_type.flags = ShaderVariableFlags::NoFlags; expected_type.rows = 3; expected_type.columns = 2; expected_type.arrayByteStride = 24; expected_type.matrixByteStride = 12; CHECK(parsed.errors.isEmpty()); REQUIRE(parsed.fixed.type.members.size() == 1); CHECK((parsed.fixed.type.members[0].type == expected_type)); parsed = BufferFormatter::ParseFormatString(lit("mat3 a;"), 0, true); expected_type = float_type; expected_type.name = "float3x3"; expected_type.flags = ShaderVariableFlags::NoFlags; expected_type.rows = 3; expected_type.columns = 3; // these are calculated with scalar packing for now, packing rules are tested separately expected_type.arrayByteStride = 36; expected_type.matrixByteStride = 12; CHECK(parsed.errors.isEmpty()); REQUIRE(parsed.fixed.type.members.size() == 1); CHECK((parsed.fixed.type.members[0].type == expected_type)); }; SECTION("Arrays (bounded and unbounded)") { ShaderConstantType expected_type; parsed = BufferFormatter::ParseFormatString(lit("float a[4];"), 0, true); expected_type = float_type; expected_type.name = "float"; expected_type.flags = ShaderVariableFlags::RowMajorMatrix; expected_type.rows = 1; expected_type.columns = 1; expected_type.elements = 4; expected_type.arrayByteStride = 4; CHECK(parsed.errors.isEmpty()); REQUIRE(parsed.fixed.type.members.size() == 1); CHECK((parsed.fixed.type.members[0].type == expected_type)); parsed = BufferFormatter::ParseFormatString(lit("float a[];"), 0, true); expected_type = float_type; expected_type.name = "float"; expected_type.flags = ShaderVariableFlags::RowMajorMatrix; expected_type.rows = 1; expected_type.columns = 1; expected_type.elements = 1; expected_type.arrayByteStride = 4; CHECK(parsed.errors.isEmpty()); CHECK(parsed.fixed.type.members.empty()); CHECK((parsed.repeating.type == expected_type)); parsed = BufferFormatter::ParseFormatString(lit("float2 a[4];"), 0, true); expected_type = float_type; expected_type.name = "float2"; expected_type.flags = ShaderVariableFlags::RowMajorMatrix; expected_type.rows = 1; expected_type.columns = 2; expected_type.elements = 4; expected_type.arrayByteStride = 8; CHECK(parsed.errors.isEmpty()); REQUIRE(parsed.fixed.type.members.size() == 1); CHECK((parsed.fixed.type.members[0].type == expected_type)); parsed = BufferFormatter::ParseFormatString(lit("float2 a[];"), 0, true); expected_type = float_type; expected_type.name = "float2"; expected_type.flags = ShaderVariableFlags::RowMajorMatrix; expected_type.rows = 1; expected_type.columns = 2; expected_type.elements = 1; expected_type.arrayByteStride = 8; CHECK(parsed.errors.isEmpty()); CHECK(parsed.fixed.type.members.empty()); CHECK((parsed.repeating.type == expected_type)); parsed = BufferFormatter::ParseFormatString(lit("float2x3 a[4];"), 0, true); expected_type = float_type; expected_type.name = "float2x3"; expected_type.flags = ShaderVariableFlags::NoFlags; expected_type.rows = 2; expected_type.columns = 3; expected_type.elements = 4; expected_type.arrayByteStride = 24; expected_type.matrixByteStride = 8; CHECK(parsed.errors.isEmpty()); REQUIRE(parsed.fixed.type.members.size() == 1); CHECK((parsed.fixed.type.members[0].type == expected_type)); parsed = BufferFormatter::ParseFormatString(lit("float2x3 a[];"), 0, true); expected_type = float_type; expected_type.name = "float2x3"; expected_type.flags = ShaderVariableFlags::NoFlags; expected_type.rows = 2; expected_type.columns = 3; expected_type.elements = 1; expected_type.arrayByteStride = 24; expected_type.matrixByteStride = 8; CHECK(parsed.errors.isEmpty()); CHECK(parsed.fixed.type.members.empty()); CHECK((parsed.repeating.type == expected_type)); }; SECTION("Legacy prefix keywords") { ShaderConstantType expected_type; parsed = BufferFormatter::ParseFormatString(lit("rgb float4 a;"), 0, true); expected_type = float_type; expected_type.name = "float4"; expected_type.flags |= ShaderVariableFlags::RGBDisplay; expected_type.columns = 4; expected_type.arrayByteStride = 16; CHECK(parsed.errors.isEmpty()); CHECK(parsed.repeating.type.members.empty()); REQUIRE(parsed.fixed.type.members.size() == 1); CHECK(parsed.fixed.type.members[0].name == "a"); CHECK((parsed.fixed.type.members[0].type == expected_type)); parsed = BufferFormatter::ParseFormatString(lit("row_major float4x4 a;"), 0, true); expected_type = float_type; expected_type.name = "float4x4"; expected_type.flags |= ShaderVariableFlags::RowMajorMatrix; expected_type.rows = 4; expected_type.columns = 4; expected_type.arrayByteStride = 64; expected_type.matrixByteStride = 16; CHECK(parsed.errors.isEmpty()); CHECK(parsed.repeating.type.members.empty()); REQUIRE(parsed.fixed.type.members.size() == 1); CHECK(parsed.fixed.type.members[0].name == "a"); CHECK((parsed.fixed.type.members[0].type == expected_type)); parsed = BufferFormatter::ParseFormatString(lit("column_major float4x4 a;"), 0, true); expected_type = float_type; expected_type.name = "float4x4"; expected_type.flags &= ~ShaderVariableFlags::RowMajorMatrix; expected_type.rows = 4; expected_type.columns = 4; expected_type.arrayByteStride = 64; expected_type.matrixByteStride = 16; CHECK(parsed.errors.isEmpty()); CHECK(parsed.repeating.type.members.empty()); REQUIRE(parsed.fixed.type.members.size() == 1); CHECK(parsed.fixed.type.members[0].name == "a"); CHECK((parsed.fixed.type.members[0].type == expected_type)); }; SECTION("unsigned separate specifier") { parsed = BufferFormatter::ParseFormatString(lit("unsigned int a;"), 0, true); CHECK(parsed.errors.isEmpty()); CHECK(parsed.repeating.type.members.empty()); REQUIRE(parsed.fixed.type.members.size() == 1); CHECK(parsed.fixed.type.members[0].name == "a"); CHECK((parsed.fixed.type.members[0].type == uint_type)); parsed = BufferFormatter::ParseFormatString(lit("unsigned short a;"), 0, true); ShaderConstantType expected_type; expected_type.name = "ushort"; expected_type.flags = ShaderVariableFlags::RowMajorMatrix; expected_type.baseType = VarType::UShort; expected_type.arrayByteStride = 2; CHECK(parsed.errors.isEmpty()); CHECK(parsed.repeating.type.members.empty()); REQUIRE(parsed.fixed.type.members.size() == 1); CHECK(parsed.fixed.type.members[0].name == "a"); CHECK((parsed.fixed.type.members[0].type == expected_type)); parsed = BufferFormatter::ParseFormatString(lit("unsigned byte a;"), 0, true); expected_type.name = "ubyte"; expected_type.baseType = VarType::UByte; expected_type.arrayByteStride = 1; CHECK(parsed.errors.isEmpty()); CHECK(parsed.repeating.type.members.empty()); REQUIRE(parsed.fixed.type.members.size() == 1); CHECK(parsed.fixed.type.members[0].name == "a"); CHECK((parsed.fixed.type.members[0].type == expected_type)); }; SECTION("Legacy base types") { SECTION("hex") { parsed = BufferFormatter::ParseFormatString(lit("xint a;"), 0, true); ShaderConstantType expected_type = uint_type; expected_type.flags |= ShaderVariableFlags::HexDisplay; CHECK(parsed.errors.isEmpty()); REQUIRE(parsed.fixed.type.members.size() == 1); CHECK((parsed.fixed.type.members[0].type == expected_type)); parsed = BufferFormatter::ParseFormatString(lit("xshort a;"), 0, true); expected_type.name = "ushort"; expected_type.baseType = VarType::UShort; expected_type.arrayByteStride = 2; CHECK(parsed.errors.isEmpty()); REQUIRE(parsed.fixed.type.members.size() == 1); CHECK((parsed.fixed.type.members[0].type == expected_type)); parsed = BufferFormatter::ParseFormatString(lit("xbyte a;"), 0, true); expected_type.name = "ubyte"; expected_type.baseType = VarType::UByte; expected_type.arrayByteStride = 1; CHECK(parsed.errors.isEmpty()); REQUIRE(parsed.fixed.type.members.size() == 1); CHECK((parsed.fixed.type.members[0].type == expected_type)); parsed = BufferFormatter::ParseFormatString(lit("xlong a;"), 0, true); expected_type.name = "ulong"; expected_type.baseType = VarType::ULong; expected_type.arrayByteStride = 8; CHECK(parsed.errors.isEmpty()); REQUIRE(parsed.fixed.type.members.size() == 1); CHECK((parsed.fixed.type.members[0].type == expected_type)); }; SECTION("unorm") { parsed = BufferFormatter::ParseFormatString(lit("unormh a;"), 0, true); ShaderConstantType expected_type; expected_type.flags = ShaderVariableFlags::RowMajorMatrix | ShaderVariableFlags::UNorm; expected_type.name = "ushort"; expected_type.baseType = VarType::UShort; expected_type.arrayByteStride = 2; CHECK(parsed.errors.isEmpty()); REQUIRE(parsed.fixed.type.members.size() == 1); CHECK((parsed.fixed.type.members[0].type == expected_type)); parsed = BufferFormatter::ParseFormatString(lit("unormb a;"), 0, true); expected_type.name = "ubyte"; expected_type.baseType = VarType::UByte; expected_type.arrayByteStride = 1; CHECK(parsed.errors.isEmpty()); REQUIRE(parsed.fixed.type.members.size() == 1); CHECK((parsed.fixed.type.members[0].type == expected_type)); }; SECTION("snorm") { parsed = BufferFormatter::ParseFormatString(lit("snormh a;"), 0, true); ShaderConstantType expected_type; expected_type.flags = ShaderVariableFlags::RowMajorMatrix | ShaderVariableFlags::SNorm; expected_type.name = "short"; expected_type.baseType = VarType::SShort; expected_type.arrayByteStride = 2; CHECK(parsed.errors.isEmpty()); REQUIRE(parsed.fixed.type.members.size() == 1); CHECK((parsed.fixed.type.members[0].type == expected_type)); parsed = BufferFormatter::ParseFormatString(lit("snormb a;"), 0, true); expected_type.name = "byte"; expected_type.baseType = VarType::SByte; expected_type.arrayByteStride = 1; CHECK(parsed.errors.isEmpty()); REQUIRE(parsed.fixed.type.members.size() == 1); CHECK((parsed.fixed.type.members[0].type == expected_type)); }; SECTION("10:10:10:2") { parsed = BufferFormatter::ParseFormatString(lit("uintten a;"), 0, true); ShaderConstantType expected_type; expected_type.flags = ShaderVariableFlags::RowMajorMatrix | ShaderVariableFlags::R10G10B10A2; expected_type.name = "uint"; expected_type.baseType = VarType::UInt; expected_type.arrayByteStride = 4; expected_type.columns = 4; CHECK(parsed.errors.isEmpty()); REQUIRE(parsed.fixed.type.members.size() == 1); CHECK((parsed.fixed.type.members[0].type == expected_type)); parsed = BufferFormatter::ParseFormatString(lit("unormten a;"), 0, true); expected_type.flags = ShaderVariableFlags::RowMajorMatrix | ShaderVariableFlags::R10G10B10A2 | ShaderVariableFlags::UNorm; expected_type.name = "uint"; expected_type.baseType = VarType::UInt; expected_type.arrayByteStride = 4; expected_type.columns = 4; CHECK(parsed.errors.isEmpty()); REQUIRE(parsed.fixed.type.members.size() == 1); CHECK((parsed.fixed.type.members[0].type == expected_type)); }; SECTION("11:11:10") { parsed = BufferFormatter::ParseFormatString(lit("floateleven a;"), 0, true); ShaderConstantType expected_type; expected_type.flags = ShaderVariableFlags::RowMajorMatrix | ShaderVariableFlags::R11G11B10; expected_type.name = "float"; expected_type.baseType = VarType::Float; expected_type.arrayByteStride = 4; expected_type.columns = 3; CHECK(parsed.errors.isEmpty()); REQUIRE(parsed.fixed.type.members.size() == 1); CHECK((parsed.fixed.type.members[0].type == expected_type)); }; }; SECTION("enums") { rdcstr def = R"( enum MyEnum : uint { Val1 = 1, Val2 = 2, ValHex = 0xf, }; MyEnum e; )"; parsed = BufferFormatter::ParseFormatString(def, 0, true); CHECK(parsed.errors.isEmpty()); REQUIRE(parsed.fixed.type.members.size() == 1); CHECK(parsed.fixed.type.members[0].name == "e"); CHECK(parsed.fixed.type.members[0].type.name == "MyEnum"); CHECK(parsed.fixed.type.members[0].type.baseType == VarType::Enum); CHECK(parsed.fixed.type.members[0].type.matrixByteStride == 4); CHECK(parsed.fixed.type.members[0].type.members[0].name == "Val1"); CHECK(parsed.fixed.type.members[0].type.members[0].defaultValue == 1); CHECK(parsed.fixed.type.members[0].type.members[1].name == "Val2"); CHECK(parsed.fixed.type.members[0].type.members[1].defaultValue == 2); CHECK(parsed.fixed.type.members[0].type.members[2].name == "ValHex"); CHECK(parsed.fixed.type.members[0].type.members[2].defaultValue == 0xf); def = R"( enum MyEnum : ushort { Val = 0, }; MyEnum e; )"; parsed = BufferFormatter::ParseFormatString(def, 0, true); CHECK(parsed.errors.isEmpty()); REQUIRE(parsed.fixed.type.members.size() == 1); CHECK(parsed.fixed.type.members[0].name == "e"); CHECK(parsed.fixed.type.members[0].type.name == "MyEnum"); CHECK(parsed.fixed.type.members[0].type.baseType == VarType::Enum); CHECK(parsed.fixed.type.members[0].type.matrixByteStride == 2); CHECK(parsed.fixed.type.members[0].type.members[0].name == "Val"); CHECK(parsed.fixed.type.members[0].type.members[0].defaultValue == 0); }; SECTION("bitfields") { rdcstr def = R"( uint firstbyte : 8; uint halfsecondbyte : 4; uint halfsecondbyte2 : 4; uint lasttwo : 16; )"; parsed = BufferFormatter::ParseFormatString(def, 0, true); CHECK(parsed.errors.isEmpty()); REQUIRE(parsed.fixed.type.members.size() == 4); CHECK(parsed.fixed.type.arrayByteStride == 4); CHECK(parsed.fixed.type.members[0].name == "firstbyte"); CHECK(parsed.fixed.type.members[0].bitFieldOffset == 0); CHECK(parsed.fixed.type.members[0].bitFieldSize == 8); CHECK((parsed.fixed.type.members[0].type == uint_type)); CHECK(parsed.fixed.type.members[1].name == "halfsecondbyte"); CHECK(parsed.fixed.type.members[1].bitFieldOffset == 8); CHECK(parsed.fixed.type.members[1].bitFieldSize == 4); CHECK((parsed.fixed.type.members[1].type == uint_type)); CHECK(parsed.fixed.type.members[2].name == "halfsecondbyte2"); CHECK(parsed.fixed.type.members[2].bitFieldOffset == 12); CHECK(parsed.fixed.type.members[2].bitFieldSize == 4); CHECK((parsed.fixed.type.members[2].type == uint_type)); CHECK(parsed.fixed.type.members[3].name == "lasttwo"); CHECK(parsed.fixed.type.members[3].bitFieldOffset == 16); CHECK(parsed.fixed.type.members[3].bitFieldSize == 16); CHECK((parsed.fixed.type.members[3].type == uint_type)); def = R"( enum e : ushort { val = 5 }; uint firstbyte : 8; uint halfsecondbyte : 4; uint halfsecondbyte2 : 4; e lastenum : 16; )"; parsed = BufferFormatter::ParseFormatString(def, 0, true); CHECK(parsed.errors.isEmpty()); REQUIRE(parsed.fixed.type.members.size() == 4); CHECK(parsed.fixed.type.arrayByteStride == 4); CHECK(parsed.fixed.type.members[0].name == "firstbyte"); CHECK(parsed.fixed.type.members[0].bitFieldOffset == 0); CHECK(parsed.fixed.type.members[0].bitFieldSize == 8); CHECK((parsed.fixed.type.members[0].type == uint_type)); CHECK(parsed.fixed.type.members[1].name == "halfsecondbyte"); CHECK(parsed.fixed.type.members[1].bitFieldOffset == 8); CHECK(parsed.fixed.type.members[1].bitFieldSize == 4); CHECK((parsed.fixed.type.members[1].type == uint_type)); CHECK(parsed.fixed.type.members[2].name == "halfsecondbyte2"); CHECK(parsed.fixed.type.members[2].bitFieldOffset == 12); CHECK(parsed.fixed.type.members[2].bitFieldSize == 4); CHECK((parsed.fixed.type.members[2].type == uint_type)); CHECK(parsed.fixed.type.members[3].name == "lastenum"); CHECK(parsed.fixed.type.members[3].byteOffset == 2); CHECK(parsed.fixed.type.members[3].bitFieldOffset == 0); CHECK(parsed.fixed.type.members[3].bitFieldSize == 16); CHECK(parsed.fixed.type.members[3].type.baseType == VarType::Enum); def = R"( uint firstbyte : 8; uint : 4; uint halfsecondbyte2 : 4; uint lasttwo : 16; )"; parsed = BufferFormatter::ParseFormatString(def, 0, true); CHECK(parsed.errors.isEmpty()); REQUIRE(parsed.fixed.type.members.size() == 3); CHECK(parsed.fixed.type.arrayByteStride == 4); CHECK(parsed.fixed.type.members[0].name == "firstbyte"); CHECK(parsed.fixed.type.members[0].bitFieldOffset == 0); CHECK(parsed.fixed.type.members[0].bitFieldSize == 8); CHECK((parsed.fixed.type.members[0].type == uint_type)); // bits skipped CHECK(parsed.fixed.type.members[1].name == "halfsecondbyte2"); CHECK(parsed.fixed.type.members[1].bitFieldOffset == 12); CHECK(parsed.fixed.type.members[1].bitFieldSize == 4); CHECK((parsed.fixed.type.members[1].type == uint_type)); CHECK(parsed.fixed.type.members[2].name == "lasttwo"); CHECK(parsed.fixed.type.members[2].bitFieldOffset == 16); CHECK(parsed.fixed.type.members[2].bitFieldSize == 16); CHECK((parsed.fixed.type.members[2].type == uint_type)); def = R"( unsigned char bit0 : 1; unsigned char bit1 : 1; unsigned char bit2 : 1; unsigned char bit3 : 1; unsigned char : 3; unsigned char highbit : 1; )"; parsed = BufferFormatter::ParseFormatString(def, 0, true); CHECK(parsed.errors.isEmpty()); REQUIRE(parsed.fixed.type.members.size() == 5); CHECK(parsed.fixed.type.arrayByteStride == 1); CHECK(parsed.fixed.type.members[0].name == "bit0"); CHECK(parsed.fixed.type.members[0].bitFieldOffset == 0); CHECK(parsed.fixed.type.members[0].bitFieldSize == 1); CHECK(parsed.fixed.type.members[1].name == "bit1"); CHECK(parsed.fixed.type.members[1].bitFieldOffset == 1); CHECK(parsed.fixed.type.members[1].bitFieldSize == 1); CHECK(parsed.fixed.type.members[2].name == "bit2"); CHECK(parsed.fixed.type.members[2].bitFieldOffset == 2); CHECK(parsed.fixed.type.members[2].bitFieldSize == 1); CHECK(parsed.fixed.type.members[3].name == "bit3"); CHECK(parsed.fixed.type.members[3].bitFieldOffset == 3); CHECK(parsed.fixed.type.members[3].bitFieldSize == 1); CHECK(parsed.fixed.type.members[4].name == "highbit"); CHECK(parsed.fixed.type.members[4].bitFieldOffset == 7); CHECK(parsed.fixed.type.members[4].bitFieldSize == 1); def = R"( uint infirst : 16; uint infirstalso : 14; // this will be forced to the second uint, leaving 2 bits of trailing padding in the first uint overflow : 20; // this then also can't be packed into the second uint and will be packed into a third, leaving 12 bits of padding uint inthird : 14; // result: total of 64 bits but packed into 3 uints due to padding )"; parsed = BufferFormatter::ParseFormatString(def, 0, true); CHECK(parsed.errors.isEmpty()); REQUIRE(parsed.fixed.type.members.size() == 4); CHECK(parsed.fixed.type.arrayByteStride == 12); CHECK(parsed.fixed.type.members[0].name == "infirst"); CHECK(parsed.fixed.type.members[0].byteOffset == 0); CHECK(parsed.fixed.type.members[0].bitFieldOffset == 0); CHECK(parsed.fixed.type.members[0].bitFieldSize == 16); CHECK(parsed.fixed.type.members[1].name == "infirstalso"); CHECK(parsed.fixed.type.members[1].byteOffset == 0); CHECK(parsed.fixed.type.members[1].bitFieldOffset == 16); CHECK(parsed.fixed.type.members[1].bitFieldSize == 14); CHECK(parsed.fixed.type.members[2].name == "overflow"); CHECK(parsed.fixed.type.members[2].byteOffset == 4); CHECK(parsed.fixed.type.members[2].bitFieldOffset == 0); CHECK(parsed.fixed.type.members[2].bitFieldSize == 20); CHECK(parsed.fixed.type.members[3].name == "inthird"); CHECK(parsed.fixed.type.members[3].byteOffset == 8); CHECK(parsed.fixed.type.members[3].bitFieldOffset == 0); CHECK(parsed.fixed.type.members[3].bitFieldSize == 14); // check that offsets are correctly processed after a bitfield that might have left some pending space def = R"( uint a : 16; uint b : 14; [[offset(12)]] uint c; )"; parsed = BufferFormatter::ParseFormatString(def, 0, true); CHECK(parsed.errors.isEmpty()); REQUIRE(parsed.fixed.type.members.size() == 3); CHECK(parsed.fixed.type.arrayByteStride == 16); CHECK(parsed.fixed.type.members[0].name == "a"); CHECK(parsed.fixed.type.members[0].byteOffset == 0); CHECK(parsed.fixed.type.members[0].bitFieldOffset == 0); CHECK(parsed.fixed.type.members[0].bitFieldSize == 16); CHECK(parsed.fixed.type.members[1].name == "b"); CHECK(parsed.fixed.type.members[1].byteOffset == 0); CHECK(parsed.fixed.type.members[1].bitFieldOffset == 16); CHECK(parsed.fixed.type.members[1].bitFieldSize == 14); CHECK(parsed.fixed.type.members[2].name == "c"); CHECK(parsed.fixed.type.members[2].byteOffset == 12); CHECK(parsed.fixed.type.members[2].bitFieldOffset == 0); CHECK(parsed.fixed.type.members[2].bitFieldSize == 0); // check that an offset mid-bitfield is properly respected def = R"( uint a : 13; [[offset(8)]] uint b : 14; )"; parsed = BufferFormatter::ParseFormatString(def, 0, true); CHECK(parsed.errors.isEmpty()); REQUIRE(parsed.fixed.type.members.size() == 2); CHECK(parsed.fixed.type.arrayByteStride == 12); CHECK(parsed.fixed.type.members[0].name == "a"); CHECK(parsed.fixed.type.members[0].byteOffset == 0); CHECK(parsed.fixed.type.members[0].bitFieldOffset == 0); CHECK(parsed.fixed.type.members[0].bitFieldSize == 13); CHECK(parsed.fixed.type.members[1].name == "b"); CHECK(parsed.fixed.type.members[1].byteOffset == 8); CHECK(parsed.fixed.type.members[1].bitFieldOffset == 0); CHECK(parsed.fixed.type.members[1].bitFieldSize == 14); // check that non-bitfield values after bitfields have the right offset calculated def = R"( struct str { int x; }; uint a : 13; uint b; uint c : 14; str d; )"; parsed = BufferFormatter::ParseFormatString(def, 0, true); CHECK(parsed.errors.isEmpty()); REQUIRE(parsed.fixed.type.members.size() == 4); CHECK(parsed.fixed.type.arrayByteStride == 16); CHECK(parsed.fixed.type.members[0].name == "a"); CHECK(parsed.fixed.type.members[0].byteOffset == 0); CHECK(parsed.fixed.type.members[0].bitFieldOffset == 0); CHECK(parsed.fixed.type.members[0].bitFieldSize == 13); CHECK(parsed.fixed.type.members[1].name == "b"); CHECK(parsed.fixed.type.members[1].byteOffset == 4); CHECK(parsed.fixed.type.members[1].bitFieldOffset == 0); CHECK(parsed.fixed.type.members[1].bitFieldSize == 0); CHECK(parsed.fixed.type.members[2].name == "c"); CHECK(parsed.fixed.type.members[2].byteOffset == 8); CHECK(parsed.fixed.type.members[2].bitFieldOffset == 0); CHECK(parsed.fixed.type.members[2].bitFieldSize == 14); CHECK(parsed.fixed.type.members[3].name == "d"); CHECK(parsed.fixed.type.members[3].byteOffset == 12); CHECK(parsed.fixed.type.members[3].bitFieldOffset == 0); CHECK(parsed.fixed.type.members[3].bitFieldSize == 0); }; SECTION("pointers") { rdcstr def = R"( struct inner { int a; float b; }; inner *ptr; int count; )"; parsed = BufferFormatter::ParseFormatString(def, 0, true); CHECK(parsed.errors.isEmpty()); REQUIRE(parsed.fixed.type.members.size() == 2); CHECK(parsed.fixed.type.members[0].name == "ptr"); CHECK(parsed.fixed.type.members[0].type.baseType == VarType::GPUPointer); CHECK(parsed.fixed.type.members[1].name == "count"); CHECK((parsed.fixed.type.members[1].type == int_type)); REQUIRE(parsed.fixed.type.members[0].type.pointerTypeID != ~0U); const ShaderConstantType &ptrType = PointerTypeRegistry::GetTypeDescriptor(parsed.fixed.type.members[0].type.pointerTypeID); REQUIRE(ptrType.members.size() == 2); CHECK(ptrType.members[0].name == "a"); CHECK((ptrType.members[0].type == int_type)); CHECK(ptrType.members[1].name == "b"); CHECK((ptrType.members[1].type == float_type)); def = R"( int *a; float *b; row_major float3x3 *c; column_major float3x3 *d; [[rgb]] xbyte3 *e; ulong2 *arr[3]; )"; parsed = BufferFormatter::ParseFormatString(def, 0, true); CHECK(parsed.errors.isEmpty()); REQUIRE(parsed.fixed.type.members.size() == 6); for(size_t i = 0; i < parsed.fixed.type.members.size(); i++) { CHECK(parsed.fixed.type.members[i].type.baseType == VarType::GPUPointer); CHECK(parsed.fixed.type.members[i].type.arrayByteStride == 8); if(parsed.fixed.type.members[i].name != "arr") CHECK(parsed.fixed.type.members[i].type.elements == 1); } CHECK(parsed.fixed.type.members[5].type.elements == 3); { ShaderConstantType innerType; int i = 0; innerType = PointerTypeRegistry::GetTypeDescriptor(parsed.fixed.type.members[i].type.pointerTypeID); REQUIRE(innerType.members.size() == 1); CHECK(innerType.name.isEmpty()); CHECK(innerType.members[0].name == parsed.fixed.type.members[i].name); innerType = innerType.members[0].type; CHECK(innerType.baseType == VarType::SInt); CHECK(innerType.rows == 1); CHECK(innerType.columns == 1); i++; innerType = PointerTypeRegistry::GetTypeDescriptor(parsed.fixed.type.members[i].type.pointerTypeID); REQUIRE(innerType.members.size() == 1); CHECK(innerType.name.isEmpty()); CHECK(innerType.members[0].name == parsed.fixed.type.members[i].name); innerType = innerType.members[0].type; CHECK(innerType.baseType == VarType::Float); CHECK(innerType.rows == 1); CHECK(innerType.columns == 1); i++; innerType = PointerTypeRegistry::GetTypeDescriptor(parsed.fixed.type.members[i].type.pointerTypeID); REQUIRE(innerType.members.size() == 1); CHECK(innerType.name.isEmpty()); CHECK(innerType.members[0].name == parsed.fixed.type.members[i].name); innerType = innerType.members[0].type; CHECK(innerType.baseType == VarType::Float); CHECK(innerType.rows == 3); CHECK(innerType.columns == 3); CHECK(innerType.flags & ShaderVariableFlags::RowMajorMatrix); i++; innerType = PointerTypeRegistry::GetTypeDescriptor(parsed.fixed.type.members[i].type.pointerTypeID); REQUIRE(innerType.members.size() == 1); CHECK(innerType.name.isEmpty()); CHECK(innerType.members[0].name == parsed.fixed.type.members[i].name); innerType = innerType.members[0].type; CHECK(innerType.baseType == VarType::Float); CHECK(innerType.rows == 3); CHECK(innerType.columns == 3); CHECK(innerType.flags == ShaderVariableFlags::NoFlags); i++; innerType = PointerTypeRegistry::GetTypeDescriptor(parsed.fixed.type.members[i].type.pointerTypeID); REQUIRE(innerType.members.size() == 1); CHECK(innerType.name.isEmpty()); CHECK(innerType.members[0].name == parsed.fixed.type.members[i].name); innerType = innerType.members[0].type; CHECK(innerType.baseType == VarType::UByte); CHECK(innerType.rows == 1); CHECK(innerType.columns == 3); CHECK(innerType.flags & ShaderVariableFlags::HexDisplay); CHECK(innerType.flags & ShaderVariableFlags::RGBDisplay); i++; innerType = PointerTypeRegistry::GetTypeDescriptor(parsed.fixed.type.members[i].type.pointerTypeID); REQUIRE(innerType.members.size() == 1); CHECK(innerType.name.isEmpty()); CHECK(innerType.members[0].name == parsed.fixed.type.members[i].name); innerType = innerType.members[0].type; CHECK(innerType.baseType == VarType::ULong); CHECK(innerType.rows == 1); CHECK(innerType.columns == 2); } def = R"( int *a; int* a; int*a; int * a; int * ; int *; int* ; int*; )"; parsed = BufferFormatter::ParseFormatString(def, 0, true); CHECK(parsed.errors.isEmpty()); REQUIRE(parsed.fixed.type.members.size() == 8); for(size_t i = 0; i < parsed.fixed.type.members.size(); i++) { CHECK(parsed.fixed.type.members[i].type.baseType == VarType::GPUPointer); CHECK(parsed.fixed.type.members[i].type.arrayByteStride == 8); CHECK(parsed.fixed.type.members[i].type.elements == 1); ShaderConstantType innerType; innerType = PointerTypeRegistry::GetTypeDescriptor(parsed.fixed.type.members[i].type.pointerTypeID); REQUIRE(innerType.members.size() == 1); CHECK(innerType.name.isEmpty()); CHECK(innerType.members[0].name == parsed.fixed.type.members[i].name); innerType = innerType.members[0].type; CHECK(innerType.baseType == VarType::SInt); CHECK(innerType.rows == 1); CHECK(innerType.columns == 1); } def = R"( struct inner { int a; }; int c : 16; int d : 16; int a : 24; int b : 8; inner *ptr; )"; parsed = BufferFormatter::ParseFormatString(def, 0, true); CHECK(parsed.errors.isEmpty()); REQUIRE(parsed.fixed.type.members.size() == 5); REQUIRE(parsed.fixed.type.arrayByteStride == 16); }; SECTION("structs") { rdcstr def = R"( struct inner { int a; float b; }; struct outer { inner first; float c; inner array[3]; float d; }; )"; parsed = BufferFormatter::ParseFormatString(def + "\nouter o;", 0, true); CHECK(parsed.errors.isEmpty()); REQUIRE(parsed.fixed.type.members.size() == 1); const ShaderConstant &o = parsed.fixed.type.members[0]; CHECK(o.name == "o"); CHECK(o.type.name == "outer"); CHECK(o.type.baseType == VarType::Struct); CHECK(o.type.arrayByteStride == 40); REQUIRE(o.type.members.size() == 4); CHECK(o.type.members[0].name == "first"); CHECK(o.type.members[0].byteOffset == 0); CHECK(o.type.members[1].name == "c"); CHECK(o.type.members[1].byteOffset == 8); CHECK((o.type.members[1].type == float_type)); CHECK(o.type.members[2].name == "array"); CHECK(o.type.members[2].byteOffset == 12); CHECK(o.type.members[3].name == "d"); CHECK(o.type.members[3].byteOffset == 36); CHECK((o.type.members[3].type == float_type)); const ShaderConstant &first = o.type.members[0]; const ShaderConstant &array = o.type.members[2]; CHECK((first.type.members == array.type.members)); REQUIRE(first.type.members.size() == 2); CHECK(first.type.members[0].name == "a"); CHECK(first.type.members[0].byteOffset == 0); CHECK((first.type.members[0].type == int_type)); CHECK(first.type.members[1].name == "b"); CHECK(first.type.members[1].byteOffset == 4); CHECK((first.type.members[1].type == float_type)); ParsedFormat parsed2 = BufferFormatter::ParseFormatString(def, 0, true); CHECK(parsed.errors.isEmpty()); REQUIRE(parsed.fixed.type.members.size() == 1); CHECK((o.type.members == parsed2.fixed.type.members)); }; SECTION("annotations") { ShaderConstantType expected_type; rdcstr def; SECTION("general annotation parsing") { parsed = BufferFormatter::ParseFormatString(lit("[[rgb]]\nfloat4 a;"), 0, true); CHECK(parsed.errors.isEmpty()); CHECK(parsed.repeating.type.members.empty()); REQUIRE(parsed.fixed.type.members.size() == 1); CHECK(parsed.fixed.type.members[0].type.flags & ShaderVariableFlags::RGBDisplay); parsed = BufferFormatter::ParseFormatString(lit("[[rgb]] float4 a;"), 0, true); CHECK(parsed.errors.isEmpty()); CHECK(parsed.repeating.type.members.empty()); REQUIRE(parsed.fixed.type.members.size() == 1); CHECK(parsed.fixed.type.members[0].type.flags & ShaderVariableFlags::RGBDisplay); parsed = BufferFormatter::ParseFormatString(lit("[[rgb]] \n \n \n float4 a;"), 0, true); CHECK(parsed.errors.isEmpty()); CHECK(parsed.repeating.type.members.empty()); REQUIRE(parsed.fixed.type.members.size() == 1); CHECK(parsed.fixed.type.members[0].type.flags & ShaderVariableFlags::RGBDisplay); parsed = BufferFormatter::ParseFormatString( lit("[[rgb]] \n // comment \n /* comment */ \n float4 a;"), 0, true); CHECK(parsed.errors.isEmpty()); CHECK(parsed.repeating.type.members.empty()); REQUIRE(parsed.fixed.type.members.size() == 1); CHECK(parsed.fixed.type.members[0].type.flags & ShaderVariableFlags::RGBDisplay); } SECTION("[[rgb]]") { parsed = BufferFormatter::ParseFormatString(lit("[[rgb]] float4 a;"), 0, true); expected_type = float_type; expected_type.name = "float4"; expected_type.flags |= ShaderVariableFlags::RGBDisplay; expected_type.columns = 4; expected_type.arrayByteStride = 16; CHECK(parsed.errors.isEmpty()); CHECK(parsed.repeating.type.members.empty()); REQUIRE(parsed.fixed.type.members.size() == 1); CHECK(parsed.fixed.type.members[0].name == "a"); CHECK((parsed.fixed.type.members[0].type == expected_type)); }; SECTION("[[hex]]") { parsed = BufferFormatter::ParseFormatString(lit("[[hex]] uint4 a;"), 0, true); expected_type = uint_type; expected_type.name = "uint4"; expected_type.flags |= ShaderVariableFlags::HexDisplay; expected_type.columns = 4; expected_type.arrayByteStride = 16; CHECK(parsed.errors.isEmpty()); CHECK(parsed.repeating.type.members.empty()); REQUIRE(parsed.fixed.type.members.size() == 1); CHECK(parsed.fixed.type.members[0].name == "a"); CHECK((parsed.fixed.type.members[0].type == expected_type)); parsed = BufferFormatter::ParseFormatString(lit("[[hexadecimal]] uint4 a;"), 0, true); expected_type = uint_type; expected_type.name = "uint4"; expected_type.flags |= ShaderVariableFlags::HexDisplay; expected_type.columns = 4; expected_type.arrayByteStride = 16; CHECK(parsed.errors.isEmpty()); CHECK(parsed.repeating.type.members.empty()); REQUIRE(parsed.fixed.type.members.size() == 1); CHECK(parsed.fixed.type.members[0].name == "a"); CHECK((parsed.fixed.type.members[0].type == expected_type)); }; SECTION("[[bin]]") { parsed = BufferFormatter::ParseFormatString(lit("[[bin]] uint4 a;"), 0, true); expected_type = uint_type; expected_type.name = "uint4"; expected_type.flags |= ShaderVariableFlags::BinaryDisplay; expected_type.columns = 4; expected_type.arrayByteStride = 16; CHECK(parsed.errors.isEmpty()); CHECK(parsed.repeating.type.members.empty()); REQUIRE(parsed.fixed.type.members.size() == 1); CHECK(parsed.fixed.type.members[0].name == "a"); CHECK((parsed.fixed.type.members[0].type == expected_type)); parsed = BufferFormatter::ParseFormatString(lit("[[binary]] uint4 a;"), 0, true); expected_type = uint_type; expected_type.name = "uint4"; expected_type.flags |= ShaderVariableFlags::BinaryDisplay; expected_type.columns = 4; expected_type.arrayByteStride = 16; CHECK(parsed.errors.isEmpty()); CHECK(parsed.repeating.type.members.empty()); REQUIRE(parsed.fixed.type.members.size() == 1); CHECK(parsed.fixed.type.members[0].name == "a"); CHECK((parsed.fixed.type.members[0].type == expected_type)); }; SECTION("[[row_major]] and [[col_major]]") { parsed = BufferFormatter::ParseFormatString(lit("[[row_major]] float4x4 a;"), 0, true); expected_type = float_type; expected_type.name = "float4x4"; expected_type.flags |= ShaderVariableFlags::RowMajorMatrix; expected_type.rows = 4; expected_type.columns = 4; expected_type.matrixByteStride = 16; expected_type.arrayByteStride = 64; CHECK(parsed.errors.isEmpty()); CHECK(parsed.repeating.type.members.empty()); REQUIRE(parsed.fixed.type.members.size() == 1); CHECK(parsed.fixed.type.members[0].name == "a"); CHECK((parsed.fixed.type.members[0].type == expected_type)); parsed = BufferFormatter::ParseFormatString(lit("[[col_major]] float4x4 a;"), 0, true); expected_type = float_type; expected_type.name = "float4x4"; expected_type.flags &= ~ShaderVariableFlags::RowMajorMatrix; expected_type.rows = 4; expected_type.columns = 4; expected_type.matrixByteStride = 16; expected_type.arrayByteStride = 64; CHECK(parsed.errors.isEmpty()); CHECK(parsed.repeating.type.members.empty()); REQUIRE(parsed.fixed.type.members.size() == 1); CHECK(parsed.fixed.type.members[0].name == "a"); CHECK((parsed.fixed.type.members[0].type == expected_type)); }; SECTION("[[unorm]] and [[snorm]]") { parsed = BufferFormatter::ParseFormatString(lit("[[unorm]] ushort4 a;"), 0, true); expected_type = uint_type; expected_type.name = "ushort4"; expected_type.flags |= ShaderVariableFlags::UNorm; expected_type.baseType = VarType::UShort; expected_type.columns = 4; expected_type.arrayByteStride = 8; CHECK(parsed.errors.isEmpty()); CHECK(parsed.repeating.type.members.empty()); REQUIRE(parsed.fixed.type.members.size() == 1); CHECK(parsed.fixed.type.members[0].name == "a"); CHECK((parsed.fixed.type.members[0].type == expected_type)); parsed = BufferFormatter::ParseFormatString(lit("[[unorm]] ubyte4 a;"), 0, true); expected_type = uint_type; expected_type.name = "ubyte4"; expected_type.flags |= ShaderVariableFlags::UNorm; expected_type.baseType = VarType::UByte; expected_type.columns = 4; expected_type.arrayByteStride = 4; parsed = BufferFormatter::ParseFormatString(lit("[[snorm]] short4 a;"), 0, true); expected_type = int_type; expected_type.name = "short4"; expected_type.flags |= ShaderVariableFlags::SNorm; expected_type.baseType = VarType::SShort; expected_type.columns = 4; expected_type.arrayByteStride = 8; CHECK(parsed.errors.isEmpty()); CHECK(parsed.repeating.type.members.empty()); REQUIRE(parsed.fixed.type.members.size() == 1); CHECK(parsed.fixed.type.members[0].name == "a"); CHECK((parsed.fixed.type.members[0].type == expected_type)); parsed = BufferFormatter::ParseFormatString(lit("[[snorm]] byte4 a;"), 0, true); expected_type = int_type; expected_type.name = "byte4"; expected_type.flags |= ShaderVariableFlags::SNorm; expected_type.baseType = VarType::SByte; expected_type.columns = 4; expected_type.arrayByteStride = 4; }; SECTION("[[packed]]") { parsed = BufferFormatter::ParseFormatString(lit("[[packed(r11g11b10)]] float3 a;"), 0, true); expected_type = float_type; expected_type.name = "float3"; expected_type.flags |= ShaderVariableFlags::R11G11B10; expected_type.columns = 3; expected_type.arrayByteStride = 4; CHECK(parsed.errors.isEmpty()); CHECK(parsed.repeating.type.members.empty()); REQUIRE(parsed.fixed.type.members.size() == 1); CHECK(parsed.fixed.type.members[0].name == "a"); CHECK((parsed.fixed.type.members[0].type == expected_type)); parsed = BufferFormatter::ParseFormatString(lit("[[packed(r10g10b10a2)]] uint4 a;"), 0, true); expected_type = uint_type; expected_type.name = "uint4"; expected_type.flags |= ShaderVariableFlags::R10G10B10A2; expected_type.columns = 4; expected_type.arrayByteStride = 4; CHECK(parsed.errors.isEmpty()); CHECK(parsed.repeating.type.members.empty()); REQUIRE(parsed.fixed.type.members.size() == 1); CHECK(parsed.fixed.type.members[0].name == "a"); CHECK((parsed.fixed.type.members[0].type == expected_type)); parsed = BufferFormatter::ParseFormatString(lit("[[packed(r10g10b10a2_uint)]] uint4 a;"), 0, true); expected_type = uint_type; expected_type.name = "uint4"; expected_type.flags |= ShaderVariableFlags::R10G10B10A2; expected_type.columns = 4; expected_type.arrayByteStride = 4; CHECK(parsed.errors.isEmpty()); CHECK(parsed.repeating.type.members.empty()); REQUIRE(parsed.fixed.type.members.size() == 1); CHECK(parsed.fixed.type.members[0].name == "a"); CHECK((parsed.fixed.type.members[0].type == expected_type)); parsed = BufferFormatter::ParseFormatString(lit("[[unorm]] [[packed(r10g10b10a2)]] uint4 a;"), 0, true); expected_type = uint_type; expected_type.name = "uint4"; expected_type.flags |= ShaderVariableFlags::R10G10B10A2 | ShaderVariableFlags::UNorm; expected_type.columns = 4; expected_type.arrayByteStride = 4; CHECK(parsed.errors.isEmpty()); CHECK(parsed.repeating.type.members.empty()); REQUIRE(parsed.fixed.type.members.size() == 1); CHECK(parsed.fixed.type.members[0].name == "a"); CHECK((parsed.fixed.type.members[0].type == expected_type)); parsed = BufferFormatter::ParseFormatString(lit("[[packed(r10g10b10a2_unorm)]] uint4 a;"), 0, true); expected_type = uint_type; expected_type.name = "uint4"; expected_type.flags |= ShaderVariableFlags::R10G10B10A2 | ShaderVariableFlags::UNorm; expected_type.columns = 4; expected_type.arrayByteStride = 4; CHECK(parsed.errors.isEmpty()); CHECK(parsed.repeating.type.members.empty()); REQUIRE(parsed.fixed.type.members.size() == 1); CHECK(parsed.fixed.type.members[0].name == "a"); CHECK((parsed.fixed.type.members[0].type == expected_type)); parsed = BufferFormatter::ParseFormatString(lit("[[snorm]] [[packed(r10g10b10a2)]] int4 a;"), 0, true); expected_type = int_type; expected_type.name = "int4"; expected_type.flags |= ShaderVariableFlags::R10G10B10A2 | ShaderVariableFlags::SNorm; expected_type.columns = 4; expected_type.arrayByteStride = 4; CHECK(parsed.errors.isEmpty()); CHECK(parsed.repeating.type.members.empty()); REQUIRE(parsed.fixed.type.members.size() == 1); CHECK(parsed.fixed.type.members[0].name == "a"); CHECK((parsed.fixed.type.members[0].type == expected_type)); parsed = BufferFormatter::ParseFormatString(lit("[[packed(r10g10b10a2_snorm)]] int4 a;"), 0, true); expected_type = int_type; expected_type.name = "int4"; expected_type.flags |= ShaderVariableFlags::R10G10B10A2 | ShaderVariableFlags::SNorm; expected_type.columns = 4; expected_type.arrayByteStride = 4; CHECK(parsed.errors.isEmpty()); CHECK(parsed.repeating.type.members.empty()); REQUIRE(parsed.fixed.type.members.size() == 1); CHECK(parsed.fixed.type.members[0].name == "a"); CHECK((parsed.fixed.type.members[0].type == expected_type)); }; SECTION("[[packed]] offsets") { def = R"( [[packed(r10g10b10a2_snorm)]] int4 a; float b; )"; parsed = BufferFormatter::ParseFormatString(def, 0, true); CHECK(parsed.errors.isEmpty()); CHECK(parsed.repeating.type.members.empty()); REQUIRE(parsed.fixed.type.members.size() == 2); CHECK(parsed.fixed.type.members[0].byteOffset == 0); CHECK(parsed.fixed.type.members[0].type.flags & ShaderVariableFlags::R10G10B10A2); CHECK(parsed.fixed.type.members[0].type.flags & ShaderVariableFlags::SNorm); CHECK(parsed.fixed.type.members[1].byteOffset == 4); def = R"( float a; [[packed(r10g10b10a2_unorm)]] uint4 b; )"; parsed = BufferFormatter::ParseFormatString(def, 0, true); CHECK(parsed.errors.isEmpty()); CHECK(parsed.repeating.type.members.empty()); REQUIRE(parsed.fixed.type.members.size() == 2); CHECK(parsed.fixed.type.members[0].byteOffset == 0); CHECK(parsed.fixed.type.members[1].byteOffset == 4); CHECK(parsed.fixed.type.members[1].type.flags & ShaderVariableFlags::R10G10B10A2); CHECK(parsed.fixed.type.members[1].type.flags & ShaderVariableFlags::UNorm); def = R"( [[packed(r11g11b10)]] float3 a; uint b; )"; parsed = BufferFormatter::ParseFormatString(def, 0, true); CHECK(parsed.errors.isEmpty()); CHECK(parsed.repeating.type.members.empty()); REQUIRE(parsed.fixed.type.members.size() == 2); CHECK(parsed.fixed.type.members[0].byteOffset == 0); CHECK(parsed.fixed.type.members[0].type.flags & ShaderVariableFlags::R11G11B10); CHECK(parsed.fixed.type.members[1].byteOffset == 4); def = R"( uint a; [[packed(r11g11b10)]] float3 b; )"; parsed = BufferFormatter::ParseFormatString(def, 0, true); CHECK(parsed.errors.isEmpty()); CHECK(parsed.repeating.type.members.empty()); REQUIRE(parsed.fixed.type.members.size() == 2); CHECK(parsed.fixed.type.members[0].byteOffset == 0); CHECK(parsed.fixed.type.members[1].byteOffset == 4); CHECK(parsed.fixed.type.members[1].type.flags & ShaderVariableFlags::R11G11B10); }; SECTION("[[single]]") { parsed = BufferFormatter::ParseFormatString(lit("float4 a;"), 0, false); expected_type = float_type; expected_type.name = "float4"; expected_type.columns = 4; expected_type.arrayByteStride = 16; CHECK(parsed.errors.isEmpty()); CHECK(parsed.fixed.type.members.empty()); CHECK(parsed.repeating.name == "a"); CHECK((parsed.repeating.type == expected_type)); parsed = BufferFormatter::ParseFormatString(lit("[[single]] float4 a;"), 0, false); expected_type = float_type; expected_type.name = "float4"; expected_type.columns = 4; expected_type.arrayByteStride = 16; CHECK(parsed.errors.isEmpty()); CHECK(parsed.repeating.type.members.empty()); REQUIRE(parsed.fixed.type.members.size() == 1); CHECK(parsed.fixed.type.members[0].name == "a"); CHECK((parsed.fixed.type.members[0].type == expected_type)); def = R"( struct Single { float4 a; }; Single s; )"; parsed = BufferFormatter::ParseFormatString(def, 0, false); expected_type = float_type; expected_type.name = "float4"; expected_type.columns = 4; expected_type.arrayByteStride = 16; CHECK(parsed.errors.isEmpty()); CHECK(parsed.fixed.type.members.empty()); CHECK(parsed.repeating.name == "s"); REQUIRE(parsed.repeating.type.members.size() == 1); CHECK(parsed.repeating.type.members[0].name == "a"); CHECK((parsed.repeating.type.members[0].type == expected_type)); def = R"( [[single]] struct Single { float4 a; }; )"; parsed = BufferFormatter::ParseFormatString(def, 0, false); expected_type = float_type; expected_type.name = "float4"; expected_type.columns = 4; expected_type.arrayByteStride = 16; CHECK(parsed.errors.isEmpty()); CHECK(parsed.repeating.type.members.empty()); REQUIRE(parsed.fixed.type.members.size() == 1); CHECK(parsed.fixed.type.members[0].name == "a"); CHECK((parsed.fixed.type.members[0].type == expected_type)); def = R"( [[fixed]] struct Single { float4 a; }; )"; parsed = BufferFormatter::ParseFormatString(def, 0, false); expected_type = float_type; expected_type.name = "float4"; expected_type.columns = 4; expected_type.arrayByteStride = 16; CHECK(parsed.errors.isEmpty()); CHECK(parsed.repeating.type.members.empty()); REQUIRE(parsed.fixed.type.members.size() == 1); CHECK(parsed.fixed.type.members[0].name == "a"); CHECK((parsed.fixed.type.members[0].type == expected_type)); def = R"( struct Single { float4 a; }; [[single]] Single s; )"; parsed = BufferFormatter::ParseFormatString(def, 0, false); expected_type = float_type; expected_type.name = "float4"; expected_type.columns = 4; expected_type.arrayByteStride = 16; CHECK(parsed.errors.isEmpty()); CHECK(parsed.repeating.type.members.empty()); REQUIRE(parsed.fixed.type.members.size() == 1); CHECK(parsed.fixed.type.members[0].name == "s"); REQUIRE(parsed.fixed.type.members[0].type.members.size() == 1); CHECK(parsed.fixed.type.members[0].type.members[0].name == "a"); CHECK((parsed.fixed.type.members[0].type.members[0].type == expected_type)); }; SECTION("[[size]]") { def = R"( [[size(128)]] struct s { float4 a; }; )"; parsed = BufferFormatter::ParseFormatString(def, 0, true); CHECK(parsed.errors.isEmpty()); CHECK(parsed.repeating.type.members.empty()); REQUIRE(parsed.fixed.type.members.size() == 1); CHECK(parsed.fixed.type.arrayByteStride == 128); def = R"( [[size(128)]] struct s { float4 a; }; s value; )"; parsed = BufferFormatter::ParseFormatString(def, 0, true); CHECK(parsed.errors.isEmpty()); CHECK(parsed.repeating.type.members.empty()); REQUIRE(parsed.fixed.type.members.size() == 1); CHECK(parsed.fixed.type.arrayByteStride == 128); def = R"( [[byte_size(128)]] struct s { float4 a; }; )"; parsed = BufferFormatter::ParseFormatString(def, 0, true); CHECK(parsed.errors.isEmpty()); CHECK(parsed.repeating.type.members.empty()); REQUIRE(parsed.fixed.type.members.size() == 1); CHECK(parsed.fixed.type.arrayByteStride == 128); }; SECTION("[[offset]]") { def = R"( struct s { [[offset(16)]] float4 a; [[offset(128)]] float4 b; }; )"; parsed = BufferFormatter::ParseFormatString(def, 0, true); CHECK(parsed.errors.isEmpty()); CHECK(parsed.repeating.type.members.empty()); REQUIRE(parsed.fixed.type.members.size() == 2); CHECK(parsed.fixed.type.members[0].byteOffset == 16); CHECK(parsed.fixed.type.members[1].byteOffset == 128); }; SECTION("[[pad]]") { def = R"( struct s { [[pad]] int4 pad; float4 a; [[padding]] int pad[24]; float4 b; }; )"; parsed = BufferFormatter::ParseFormatString(def, 0, true); CHECK(parsed.errors.isEmpty()); CHECK(parsed.repeating.type.members.empty()); REQUIRE(parsed.fixed.type.members.size() == 2); CHECK(parsed.fixed.type.members[0].byteOffset == 16); CHECK(parsed.fixed.type.members[1].byteOffset == 128); }; }; SECTION("packing rules") { SECTION("Check API defaults") { BufferFormatter::Init(GraphicsAPI::D3D11); parsed = BufferFormatter::ParseFormatString(lit("float a;"), 0, true); CHECK((parsed.packing == Packing::D3DCB)); BufferFormatter::Init(GraphicsAPI::D3D11); parsed = BufferFormatter::ParseFormatString(lit("float a;"), 0, false); CHECK((parsed.packing == Packing::D3DUAV)); BufferFormatter::Init(GraphicsAPI::D3D12); parsed = BufferFormatter::ParseFormatString(lit("float a;"), 0, true); CHECK((parsed.packing == Packing::D3DCB)); BufferFormatter::Init(GraphicsAPI::D3D12); parsed = BufferFormatter::ParseFormatString(lit("float a;"), 0, false); CHECK((parsed.packing == Packing::D3DUAV)); BufferFormatter::Init(GraphicsAPI::OpenGL); parsed = BufferFormatter::ParseFormatString(lit("float a;"), 0, true); CHECK((parsed.packing == Packing::std140)); BufferFormatter::Init(GraphicsAPI::OpenGL); parsed = BufferFormatter::ParseFormatString(lit("float a;"), 0, false); CHECK((parsed.packing == Packing::std430)); }; SECTION("Overriding API defaults") { BufferFormatter::Init(GraphicsAPI::D3D11); parsed = BufferFormatter::ParseFormatString(lit("#pack(c)\nfloat a;"), 0, true); CHECK((parsed.packing == Packing::C)); BufferFormatter::Init(GraphicsAPI::D3D11); parsed = BufferFormatter::ParseFormatString(lit("#pack(c)\nfloat a;"), 0, false); CHECK((parsed.packing == Packing::C)); BufferFormatter::Init(GraphicsAPI::D3D12); parsed = BufferFormatter::ParseFormatString(lit("#pack(c)\nfloat a;"), 0, true); CHECK((parsed.packing == Packing::C)); BufferFormatter::Init(GraphicsAPI::D3D12); parsed = BufferFormatter::ParseFormatString(lit("#pack(c)\nfloat a;"), 0, false); CHECK((parsed.packing == Packing::C)); BufferFormatter::Init(GraphicsAPI::OpenGL); parsed = BufferFormatter::ParseFormatString(lit("#pack(c)\nfloat a;"), 0, true); CHECK((parsed.packing == Packing::C)); BufferFormatter::Init(GraphicsAPI::OpenGL); parsed = BufferFormatter::ParseFormatString(lit("#pack(c)\nfloat a;"), 0, false); CHECK((parsed.packing == Packing::C)); }; SECTION("Parsing") { BufferFormatter::Init(GraphicsAPI::OpenGL); parsed = BufferFormatter::ParseFormatString(lit("#pack (c)\nfloat a;"), 0, false); CHECK((parsed.packing == Packing::C)); BufferFormatter::Init(GraphicsAPI::OpenGL); parsed = BufferFormatter::ParseFormatString(lit("# pack (c)\nfloat a;"), 0, false); CHECK((parsed.packing == Packing::C)); BufferFormatter::Init(GraphicsAPI::OpenGL); parsed = BufferFormatter::ParseFormatString( lit("# /*comm*/ pack /* comments */ (c)\nfloat a;"), 0, false); CHECK((parsed.packing == Packing::C)); }; SECTION("Selecting packing rules") { // this will produce different offsets on every packing rule rdcstr def = R"( struct inner { float first; byte second; }; struct s { int a; float3 b; // if vectors are aligned to components this is tightly packed, otherwise padded [[offset(32)]] float c; float4 d; // if vectors can straddle this is tightly packed, otherwise padded [[offset(64)]] float e[5]; float f; // this will be placed differently depending on whether there are tight arrays, and if // there aren't tight arrays whether trailing padding can be overlapped [[offset(160)]] inner g; byte h; // if trailing padding can be overlapped this will be 'inside' g }; )"; parsed = BufferFormatter::ParseFormatString(lit("#pack(cbuffer)\n") + def, 0, true); CHECK(parsed.errors.isEmpty()); CHECK(parsed.repeating.type.members.empty()); REQUIRE(parsed.fixed.type.members.size() == 8); CHECK(parsed.fixed.type.members[0].byteOffset == 0); // a CHECK(parsed.fixed.type.members[1].byteOffset == 4); // b CHECK(parsed.fixed.type.members[2].byteOffset == 32); // c CHECK(parsed.fixed.type.members[3].byteOffset == 48); // d CHECK(parsed.fixed.type.members[4].byteOffset == 64); // e CHECK(parsed.fixed.type.members[5].byteOffset == 132); // f CHECK(parsed.fixed.type.members[6].byteOffset == 160); // g CHECK(parsed.fixed.type.members[7].byteOffset == 165); // h parsed = BufferFormatter::ParseFormatString(lit("#pack(d3duav)\n") + def, 0, true); CHECK(parsed.errors.isEmpty()); CHECK(parsed.repeating.type.members.empty()); REQUIRE(parsed.fixed.type.members.size() == 8); CHECK(parsed.fixed.type.members[0].byteOffset == 0); // a CHECK(parsed.fixed.type.members[1].byteOffset == 4); // b CHECK(parsed.fixed.type.members[2].byteOffset == 32); // c CHECK(parsed.fixed.type.members[3].byteOffset == 36); // d CHECK(parsed.fixed.type.members[4].byteOffset == 64); // e CHECK(parsed.fixed.type.members[5].byteOffset == 84); // f CHECK(parsed.fixed.type.members[6].byteOffset == 160); // g CHECK(parsed.fixed.type.members[7].byteOffset == 168); // h parsed = BufferFormatter::ParseFormatString(lit("#pack(std140)\n") + def, 0, true); CHECK(parsed.errors.isEmpty()); CHECK(parsed.repeating.type.members.empty()); REQUIRE(parsed.fixed.type.members.size() == 8); CHECK(parsed.fixed.type.members[0].byteOffset == 0); // a CHECK(parsed.fixed.type.members[1].byteOffset == 16); // b CHECK(parsed.fixed.type.members[2].byteOffset == 32); // c CHECK(parsed.fixed.type.members[3].byteOffset == 48); // d CHECK(parsed.fixed.type.members[4].byteOffset == 64); // e CHECK(parsed.fixed.type.members[5].byteOffset == 144); // f CHECK(parsed.fixed.type.members[6].byteOffset == 160); // g CHECK(parsed.fixed.type.members[7].byteOffset == 176); // h parsed = BufferFormatter::ParseFormatString(lit("#pack(std430)\n") + def, 0, true); CHECK(parsed.errors.isEmpty()); CHECK(parsed.repeating.type.members.empty()); REQUIRE(parsed.fixed.type.members.size() == 8); CHECK(parsed.fixed.type.members[0].byteOffset == 0); // a CHECK(parsed.fixed.type.members[1].byteOffset == 16); // b CHECK(parsed.fixed.type.members[2].byteOffset == 32); // c CHECK(parsed.fixed.type.members[3].byteOffset == 48); // d CHECK(parsed.fixed.type.members[4].byteOffset == 64); // e CHECK(parsed.fixed.type.members[5].byteOffset == 84); // f CHECK(parsed.fixed.type.members[6].byteOffset == 160); // g CHECK(parsed.fixed.type.members[7].byteOffset == 168); // h parsed = BufferFormatter::ParseFormatString(lit("#pack(scalar)\n") + def, 0, true); CHECK(parsed.errors.isEmpty()); CHECK(parsed.repeating.type.members.empty()); REQUIRE(parsed.fixed.type.members.size() == 8); CHECK(parsed.fixed.type.members[0].byteOffset == 0); // a CHECK(parsed.fixed.type.members[1].byteOffset == 4); // b CHECK(parsed.fixed.type.members[2].byteOffset == 32); // c CHECK(parsed.fixed.type.members[3].byteOffset == 36); // d CHECK(parsed.fixed.type.members[4].byteOffset == 64); // e CHECK(parsed.fixed.type.members[5].byteOffset == 84); // f CHECK(parsed.fixed.type.members[6].byteOffset == 160); // g CHECK(parsed.fixed.type.members[7].byteOffset == 165); // h }; SECTION("Additional packing rules") { rdcstr def = R"( #pack(tight_bitfield_packing) uint infirst : 16; uint infirstalso : 14; // this will span 2 bits in the first uint and 18 bits in the second uint overflow : 20; uint insecond : 14; )"; for(rdcstr ruleset : {"", "#pack(c)", "#pack(scalar)", "#pack(std430)", "#pack(std140)", "#pack(cbuffer)"}) { parsed = BufferFormatter::ParseFormatString(ruleset + "\n" + def, 0, true); CHECK(parsed.errors.isEmpty()); REQUIRE(parsed.fixed.type.members.size() == 4); CHECK(parsed.fixed.type.arrayByteStride == 8); CHECK(parsed.fixed.type.members[0].name == "infirst"); CHECK(parsed.fixed.type.members[0].byteOffset == 0); CHECK(parsed.fixed.type.members[0].bitFieldOffset == 0); CHECK(parsed.fixed.type.members[0].bitFieldSize == 16); CHECK(parsed.fixed.type.members[1].name == "infirstalso"); CHECK(parsed.fixed.type.members[1].byteOffset == 0); CHECK(parsed.fixed.type.members[1].bitFieldOffset == 16); CHECK(parsed.fixed.type.members[1].bitFieldSize == 14); CHECK(parsed.fixed.type.members[2].name == "overflow"); CHECK(parsed.fixed.type.members[2].byteOffset == 3); CHECK(parsed.fixed.type.members[2].bitFieldOffset == 6); CHECK(parsed.fixed.type.members[2].bitFieldSize == 20); CHECK(parsed.fixed.type.members[3].name == "insecond"); CHECK(parsed.fixed.type.members[3].byteOffset == 6); CHECK(parsed.fixed.type.members[3].bitFieldOffset == 2); CHECK(parsed.fixed.type.members[3].bitFieldSize == 14); } }; SECTION("Testing trailing member alignments") { rdcstr def = R"( #pack(std140) struct blah { float4x4 a; float4 b; }; )"; parsed = BufferFormatter::ParseFormatString(def, 0, true); CHECK(parsed.errors.isEmpty()); CHECK(parsed.repeating.type.members.empty()); REQUIRE(parsed.fixed.type.members.size() == 2); CHECK(parsed.fixed.type.members[0].byteOffset == 0); // a CHECK(parsed.fixed.type.members[1].byteOffset == 64); // b CHECK(parsed.fixed.type.arrayByteStride == 80); // stride 80 def = R"( #pack(std140) struct blah { float4x4 a; uint b; uint c; uint d; uint e; }; )"; parsed = BufferFormatter::ParseFormatString(def, 0, true); CHECK(parsed.errors.isEmpty()); CHECK(parsed.repeating.type.members.empty()); REQUIRE(parsed.fixed.type.members.size() == 5); CHECK(parsed.fixed.type.members[0].byteOffset == 0); // a CHECK(parsed.fixed.type.members[1].byteOffset == 64); // b CHECK(parsed.fixed.type.members[2].byteOffset == 68); // c CHECK(parsed.fixed.type.members[3].byteOffset == 72); // d CHECK(parsed.fixed.type.members[4].byteOffset == 76); // e CHECK(parsed.fixed.type.arrayByteStride == 80); // stride 80 def = R"( #pack(std140) struct blah { float4x4 a; uint b; uint c; float d; uint e; }; )"; parsed = BufferFormatter::ParseFormatString(def, 0, true); CHECK(parsed.errors.isEmpty()); CHECK(parsed.repeating.type.members.empty()); REQUIRE(parsed.fixed.type.members.size() == 5); CHECK(parsed.fixed.type.members[0].byteOffset == 0); // a CHECK(parsed.fixed.type.members[1].byteOffset == 64); // b CHECK(parsed.fixed.type.members[2].byteOffset == 68); // c CHECK(parsed.fixed.type.members[3].byteOffset == 72); // d CHECK(parsed.fixed.type.members[4].byteOffset == 76); // e CHECK(parsed.fixed.type.arrayByteStride == 80); // stride 80 }; }; SECTION("errors") { rdcstr def; // we don't check exact error text, we check that an error is found and on the right line, and // contains a keyword indicating that the error is the right place. This avoids needing to // update the test every time the error text changes. // note line numbers are 0-based struct error_expect { rdcstr text; int line; rdcstr error; }; rdcarray errors; SECTION("line numbers are accurate around whitespace and comments") { errors = { {"flibble boo;", 0, "parse declaration"}, {R"( flibble boo; )", 1, "parse declaration"}, {R"( flibble boo; )", 4, "parse declaration"}, {R"( /* comments */ flibble boo; )", 4, "parse declaration"}, {R"( // // comments // flibble boo; )", 4, "parse declaration"}, {R"( // // comments // flibble /* */boo; )", 5, "parse declaration"}, {R"( // // comments // flibble /* */ boo; )", 7, "parse declaration"}, }; }; SECTION("pre-processor specifiers") { errors = { {R"( #pack(unknown) struct s { float a; }; s data; )", 1, "packing rule"}, {R"( #foo struct s { float a; }; s data; )", 1, "pre-processor"}, {R"( #pack (cbuffer) struct s { float a; }; s data; )", 1, "pre-processor"}, {R"( #pack(std140) struct s { float a; #pack(scalar) float3 b; }; s data; )", 5, "global scope"}, }; }; SECTION("annotation errors") { errors = { {R"( [[foo]] struct s { float a; }; s data; )", 2, "unrecognised annotation"}, {R"( [[pad]] struct s { float a; }; s data; )", 2, "unrecognised annotation"}, {R"( [[foo]] enum e1 : uint { val = 1; }; e1 data; )", 2, "unrecognised annotation"}, {R"( [[pad]] enum e1 : uint { val = 1; }; e1 data; )", 2, "unrecognised annotation"}, {R"( struct s { [[size]]float a; }; s data; )", 2, "unrecognised annotation"}, {R"( enum e1 : uint { [[pad]]val = 1; }; e1 data; )", 2, "unrecognised annotation"}, {R"( [[size]]float a; )", 1, "unrecognised annotation"}, {R"( byte a : 4; [[size]]byte : 4; )", 2, "unrecognised annotation"}, {R"( [[size]] struct s { float a; }; s data; )", 2, "requires a parameter"}, {R"( [[byte_size]] struct s { float a; }; s data; )", 2, "requires a parameter"}, {R"( struct s { [[offset]]float a; }; s data; )", 2, "requires a parameter"}, {R"( struct s { [[byte_offset]]float a; }; s data; )", 2, "requires a parameter"}, {R"( [[size(16)]] struct s { float a[100]; }; s data; )", 4, "less than derived"}, {R"( struct s { float a[100]; [[offset(16)]] float b; }; s data; )", 4, "overlaps with"}, {R"( struct inner { int a; } struct s { float a[100]; [[offset(16)]] inner b; }; s data; )", 6, "overlaps with"}, {R"( struct inner { int a; } struct s { float a[100]; [[offset(16)]] inner *b; }; s data; )", 6, "overlaps with"}, {R"( enum e : uint { val = 1; } struct s { float a[100]; [[offset(16)]] e b; }; s data; )", 6, "overlaps with"}, {R"( struct s { [[packed]]uint4 a; }; s data; )", 2, "requires a parameter"}, {R"( struct s { [[packed(r1g2b13a16)]]uint4 a; }; s data; )", 2, "unrecognised format"}, {R"( [[single]] float a; [[single]] float b; )", 2, "only one"}, {R"( [[single]] float a[]; )", 1, "unbounded"}, {R"( struct s { [[single]]uint4 a; }; s data; )", 2, "global variables"}, {R"( [[single]] struct s { uint4 a; }; s data; )", 6, "only defined"}, {R"( [[hex]] float a; )", 1, "floating point"}, {R"( [[hex]] [[packed(r10g10b10a2)]] uint4 a; )", 1, "packed"}, {R"( [[bin]] float a; )", 1, "floating point"}, {R"( [[bin]] [[packed(r10g10b10a2)]] uint4 a; )", 1, "packed"}, {R"( [[row_major]] float a; )", 1, "matrices"}, {R"( [[col_major]] float a; )", 1, "matrices"}, {R"( [[packed(r11g11b10)]] float a; )", 1, "float3"}, {R"( [[packed(r11g11b10)]] uint3 a; )", 1, "float3"}, {R"( [[packed(r10g10b10a2)]] float a; )", 1, "uint4"}, {R"( [[packed(r10g10b10a2)]] float4 a; )", 1, "uint4"}, {R"( [[packed(r10g10b10a2_unorm)]] float4 a; )", 1, "uint4"}, {R"( [[packed(r10g10b10a2_snorm)]] uint4 a; )", 1, "int4"}, }; }; SECTION("bitfield errors") { errors = { {R"( struct inner { int val; } int a : 8; inner *b : 24; )", 4, "packed into a bitfield"}, {R"( struct inner { int val; } int a : 8; inner b : 24; )", 4, "packed into a bitfield"}, {R"( int a : 8; byte b[3] : 24; )", 2, "packed into a bitfield"}, {R"( int a : 8; float b : 24; )", 2, "packed into a bitfield"}, {R"( int a : 8; byte3 b : 24; )", 2, "packed into a bitfield"}, {R"( int a : 8; byte2x2 b : 24; )", 2, "packed into a bitfield"}, {R"( int a : 8; [[packed(r10g10b10a2)]] uint4 b : 24; )", 2, "packed into a bitfield"}, {R"( byte a : 8; byte b : 16; )", 2, "only has 8 bits"}, }; }; SECTION("variable declaration errors") { errors = { {R"( struct s { float a; }; struct s { int a; }; s data; )", 4, "already been"}, {R"( enum e { val = 1, }; e data; )", 1, "base type"}, {R"( enum e : foo { val = 1, }; e data; )", 1, "integer type"}, {R"( enum e : uint { val = blah, }; e data; )", 2, "value declaration"}, {R"( enum e : uint { val = , }; e data; )", 2, "value declaration"}, {R"( enum e : uint { val, }; e data; )", 2, "value declaration"}, {R"( enum e : uint { val = 1, val2 = val+1, }; e data; )", 3, "value declaration"}, {R"( struct s { float a; }; s **data; )", 5, "single pointer"}, {R"( struct s { float a; }; t data; )", 5, "unrecognised type"}, {R"( blah data; )", 1, "unrecognised type"}, {R"( struct s { float a; }; s data[2][2]; )", 5, "invalid declaration"}, {R"( float a int b; )", 2, "multiple declarations"}, }; }; for(const error_expect &err : errors) { parsed = BufferFormatter::ParseFormatString(err.text, 0, true); REQUIRE(parsed.errors.contains(err.line)); // Failed to parse declaration CHECK(parsed.errors[err.line].contains(err.error, Qt::CaseInsensitive)); } }; }; #endif