1 // 2 // Copyright 2021 The ANGLE Project Authors. All rights reserved. 3 // Use of this source code is governed by a BSD-style license that can be 4 // found in the LICENSE file. 5 // 6 // BuildSPIRV: Helper for OutputSPIRV to build SPIR-V. 7 // 8 9 #ifndef COMPILER_TRANSLATOR_BUILDSPIRV_H_ 10 #define COMPILER_TRANSLATOR_BUILDSPIRV_H_ 11 12 #include "common/FixedVector.h" 13 #include "common/hash_utils.h" 14 #include "common/spirv/spirv_instruction_builder_autogen.h" 15 #include "compiler/translator/Compiler.h" 16 17 namespace spirv = angle::spirv; 18 19 namespace sh 20 { 21 // Helper classes to map types to ids 22 23 // The same GLSL type may map to multiple SPIR-V types when said GLSL type is used differently in 24 // the shader source, for example used with |invariant| and without, used in an interface block etc. 25 // This type contains the pieces of information that differentiate SPIR-V types derived from the 26 // same GLSL type. This is referred to as "SPIR-V type specialization" henceforth. 27 struct SpirvType; 28 class SpirvTypeSpec 29 { 30 public: 31 // Some of the properties that specialize SPIR-V types apply to structs or arrays, but not to 32 // their fields or basic types. When extracting fields, array elements, columns or basic types 33 // from a type, the following helpers are used to remove any ineffective (and thus incorrect) 34 // specialization. 35 void inferDefaults(const TType &type, TCompiler *compiler); 36 void onArrayElementSelection(bool isElementTypeBlock, bool isElementTypeArray); 37 void onBlockFieldSelection(const TType &fieldType); 38 void onMatrixColumnSelection(); 39 void onVectorComponentSelection(); 40 41 // If a structure is used in two interface blocks with different layouts, it would have 42 // to generate two SPIR-V types, as its fields' Offset decorations could be different. 43 // For non-block types, when used in an interface block as an array, they could generate 44 // different ArrayStride decorations. As such, the block storage is part of the SPIR-V type 45 // except for non-block non-array types. 46 TLayoutBlockStorage blockStorage = EbsUnspecified; 47 48 // If a structure is used in two I/O blocks or output varyings with and without the invariant 49 // qualifier, it would also have to generate two SPIR-V types, as its fields' Invariant 50 // decorations would be different. 51 bool isInvariantBlock = false; 52 53 // Similarly, a structure containing matrices may be used both with the column_major and 54 // row_major layout qualifier, generating two SPIR-V types with different decorations on its 55 // fields. 56 bool isRowMajorQualifiedBlock = false; 57 58 // Arrays when used in an interface block produce a different type which is decorated with an 59 // ArrayStride. Row-major qualified arrays of matrices can potentially produce a different 60 // stride from column-major ones. 61 bool isRowMajorQualifiedArray = false; 62 63 // Bool is disallowed in interface blocks in SPIR-V. This type is emulated with uint. This 64 // property applies to both blocks with bools in them and the bool type inside the block itself. 65 bool isOrHasBoolInInterfaceBlock = false; 66 }; 67 68 struct SpirvType 69 { 70 // If struct or interface block, the type is identified by the pointer. Note that both 71 // TStructure and TInterfaceBlock inherit from TFieldListCollection, and their difference is 72 // irrelevant as far as SPIR-V type is concerned. 73 const TFieldListCollection *block = nullptr; 74 75 // Otherwise, it's a basic type + column, row and array dimensions, or it's an image 76 // declaration. 77 // 78 // Notes: 79 // 80 // - `precision` turns into a RelaxedPrecision decoration on the variable and instructions. 81 // - `precise` turns into a NoContraction decoration on the instructions. 82 // - `readonly`, `writeonly`, `coherent`, `volatile` and `restrict` only apply to memory object 83 // declarations 84 // - `invariant` only applies to variable or members of a block 85 // - `matrixPacking` only applies to members of a struct 86 TBasicType type = EbtFloat; 87 88 uint8_t primarySize = 1; 89 uint8_t secondarySize = 1; 90 91 TSpan<const unsigned int> arraySizes; 92 93 // Only useful for image types. 94 TLayoutImageInternalFormat imageInternalFormat = EiifUnspecified; 95 96 // For sampled images (i.e. GLSL samplers), there are two type ids; one is the OpTypeImage that 97 // declares the image itself, and one OpTypeSampledImage. `isSamplerBaseImage` distinguishes 98 // between these two types. Note that for the former, the basic type is still Ebt*Sampler* to 99 // distinguish it from storage images (which have a basic type of Ebt*Image*). 100 bool isSamplerBaseImage = false; 101 102 // Anything that can cause the same GLSL type to produce different SPIR-V types. 103 SpirvTypeSpec typeSpec; 104 }; 105 106 struct SpirvIdAndIdList 107 { 108 spirv::IdRef id; 109 spirv::IdRefList idList; 110 111 bool operator==(const SpirvIdAndIdList &other) const 112 { 113 return id == other.id && idList == other.idList; 114 } 115 }; 116 117 struct SpirvIdAndStorageClass 118 { 119 spirv::IdRef id; 120 spv::StorageClass storageClass; 121 122 bool operator==(const SpirvIdAndStorageClass &other) const 123 { 124 return id == other.id && storageClass == other.storageClass; 125 } 126 }; 127 128 struct SpirvTypeHash 129 { operatorSpirvTypeHash130 size_t operator()(const sh::SpirvType &type) const 131 { 132 // Block storage must only affect the type if it's a block type or array type (in a block). 133 ASSERT(type.typeSpec.blockStorage == sh::EbsUnspecified || type.block != nullptr || 134 !type.arraySizes.empty()); 135 136 // Invariant must only affect the type if it's a block type. 137 ASSERT(!type.typeSpec.isInvariantBlock || type.block != nullptr); 138 139 // Row-major block must only affect the type if it's a block type. 140 ASSERT(!type.typeSpec.isRowMajorQualifiedBlock || type.block != nullptr); 141 142 // Row-major array must only affect the type if it's an array of non-square matrices in 143 // an std140 or std430 block. 144 ASSERT(!type.typeSpec.isRowMajorQualifiedArray || 145 (type.block == nullptr && !type.arraySizes.empty() && type.secondarySize > 1 && 146 type.primarySize != type.secondarySize && 147 type.typeSpec.blockStorage != sh::EbsUnspecified)); 148 149 size_t result = 0; 150 151 if (!type.arraySizes.empty()) 152 { 153 result = angle::ComputeGenericHash(type.arraySizes.data(), 154 type.arraySizes.size() * sizeof(type.arraySizes[0])); 155 } 156 157 if (type.block != nullptr) 158 { 159 return result ^ angle::ComputeGenericHash(&type.block, sizeof(type.block)) ^ 160 static_cast<size_t>(type.typeSpec.isInvariantBlock) ^ 161 (static_cast<size_t>(type.typeSpec.isRowMajorQualifiedBlock) << 1) ^ 162 (static_cast<size_t>(type.typeSpec.isRowMajorQualifiedArray) << 2) ^ 163 (type.typeSpec.blockStorage << 3); 164 } 165 166 static_assert(sh::EbtLast < 256, "Basic type doesn't fit in uint8_t"); 167 static_assert(sh::EbsLast < 8, "Block storage doesn't fit in 3 bits"); 168 static_assert(sh::EiifLast < 32, "Image format doesn't fit in 5 bits"); 169 ASSERT(type.primarySize > 0 && type.primarySize <= 4); 170 ASSERT(type.secondarySize > 0 && type.secondarySize <= 4); 171 172 const uint8_t properties[4] = { 173 static_cast<uint8_t>(type.type), 174 static_cast<uint8_t>((type.primarySize - 1) | (type.secondarySize - 1) << 2 | 175 type.isSamplerBaseImage << 4), 176 static_cast<uint8_t>(type.typeSpec.blockStorage | type.imageInternalFormat << 3), 177 // Padding because ComputeGenericHash expects a key size divisible by 4 178 }; 179 180 return result ^ angle::ComputeGenericHash(properties, sizeof(properties)); 181 } 182 }; 183 184 struct SpirvIdAndIdListHash 185 { operatorSpirvIdAndIdListHash186 size_t operator()(const SpirvIdAndIdList &key) const 187 { 188 return angle::ComputeGenericHash(key.idList.data(), 189 key.idList.size() * sizeof(key.idList[0])) ^ 190 key.id; 191 } 192 }; 193 194 struct SpirvIdAndStorageClassHash 195 { operatorSpirvIdAndStorageClassHash196 size_t operator()(const SpirvIdAndStorageClass &key) const 197 { 198 ASSERT(key.storageClass < 16); 199 return key.storageClass | key.id << 4; 200 } 201 }; 202 203 // Data tracked per SPIR-V type (keyed by SpirvType). 204 struct SpirvTypeData 205 { 206 // The SPIR-V id corresponding to the type. 207 spirv::IdRef id; 208 }; 209 210 // Decorations to be applied to variable or intermediate ids which are not part of the SPIR-V type 211 // and are not specific enough (like DescriptorSet) to be handled automatically. Currently, these 212 // are: 213 // 214 // RelaxedPrecision: used to implement |lowp| and |mediump| 215 // NoContraction: used to implement |precise|. TODO: support this. It requires the precise 216 // property to be promoted through the nodes in the AST, which currently isn't. 217 // http://anglebug.com/4889 218 // Invariant: used to implement |invariant|, which is applied to output variables. 219 // 220 // Note that Invariant applies to variables and NoContraction to arithmetic instructions, so they 221 // are mutually exclusive and a maximum of 2 decorations are possible. FixedVector::push_back will 222 // ASSERT if the given size is ever not enough. 223 using SpirvDecorations = angle::FixedVector<spv::Decoration, 2>; 224 225 // A block of code. SPIR-V produces forward references to blocks, such as OpBranchConditional 226 // specifying the id of the if and else blocks, each of those referencing the id of the block after 227 // the else. Additionally, local variable declarations are accumulated at the top of the first 228 // block in a function. For these reasons, each block of SPIR-V is generated separately and 229 // assembled at the end of the function, allowing prior blocks to be modified when necessary. 230 struct SpirvBlock 231 { 232 // Id of the block 233 spirv::IdRef labelId; 234 235 // Local variable declarations. Only the first block of a function is allowed to contain any 236 // instructions here. 237 spirv::Blob localVariables; 238 239 // Everything *after* OpLabel (which itself is not generated until blocks are assembled) and 240 // local variables. 241 spirv::Blob body; 242 243 // Whether the block is terminated. Useful for functions without return, asserting that code is 244 // not added after return/break/continue etc (i.e. dead code, which should really be removed 245 // earlier by a transformation, but could also be hacked by returning a bogus block to contain 246 // all the "garbage" to throw away), last switch case without a break, etc. 247 bool isTerminated = false; 248 }; 249 250 // Conditional code, constituting ifs, switches and loops. 251 struct SpirvConditional 252 { 253 // The id of blocks that make up the conditional. 254 // 255 // - For if, there are three blocks: the then, else and merge blocks 256 // - For loops, there are four blocks: the condition, body, continue and merge blocks 257 // - For switch, there are a number of blocks based on the cases. 258 // 259 // In all cases, the merge block is the last block in this list. When the conditional is done 260 // with, that's the block that will be made "current" and future instructions written to. The 261 // merge block is also the branch target of "break" instructions. 262 // 263 // For loops, the continue target block is the one before last block in this list. 264 std::vector<spirv::IdRef> blockIds; 265 266 // Up to which block is already generated. Used by nextConditionalBlock() to generate a block 267 // and give it an id pre-determined in blockIds. 268 size_t nextBlockToWrite = 0; 269 270 // Used to determine if continue will affect this (i.e. it's a loop). 271 bool isContinuable = false; 272 // Used to determine if break will affect this (i.e. it's a loop or switch). 273 bool isBreakable = false; 274 }; 275 276 // Helper class to construct SPIR-V 277 class SPIRVBuilder : angle::NonCopyable 278 { 279 public: SPIRVBuilder(TCompiler * compiler,ShCompileOptions compileOptions,bool forceHighp,ShHashFunction64 hashFunction,NameMap & nameMap)280 SPIRVBuilder(TCompiler *compiler, 281 ShCompileOptions compileOptions, 282 bool forceHighp, 283 ShHashFunction64 hashFunction, 284 NameMap &nameMap) 285 : mCompiler(compiler), 286 mCompileOptions(compileOptions), 287 mShaderType(gl::FromGLenum<gl::ShaderType>(compiler->getShaderType())), 288 mDisableRelaxedPrecision(forceHighp), 289 mNextAvailableId(1), 290 mHashFunction(hashFunction), 291 mNameMap(nameMap), 292 mNextUnusedBinding(0), 293 mNextUnusedInputLocation(0), 294 mNextUnusedOutputLocation(0) 295 {} 296 297 spirv::IdRef getNewId(const SpirvDecorations &decorations); 298 SpirvType getSpirvType(const TType &type, const SpirvTypeSpec &typeSpec) const; 299 const SpirvTypeData &getTypeData(const TType &type, const SpirvTypeSpec &typeSpec); 300 const SpirvTypeData &getSpirvTypeData(const SpirvType &type, const TSymbol *block); 301 spirv::IdRef getBasicTypeId(TBasicType basicType, size_t size); 302 spirv::IdRef getTypePointerId(spirv::IdRef typeId, spv::StorageClass storageClass); 303 spirv::IdRef getFunctionTypeId(spirv::IdRef returnTypeId, const spirv::IdRefList ¶mTypeIds); 304 305 // Decorations that may apply to intermediate instructions (in addition to variables). 306 SpirvDecorations getDecorations(const TType &type); 307 308 // Extended instructions 309 spirv::IdRef getExtInstImportIdStd(); 310 getSpirvDebug()311 spirv::Blob *getSpirvDebug() { return &mSpirvDebug; } getSpirvDecorations()312 spirv::Blob *getSpirvDecorations() { return &mSpirvDecorations; } getSpirvTypeAndConstantDecls()313 spirv::Blob *getSpirvTypeAndConstantDecls() { return &mSpirvTypeAndConstantDecls; } getSpirvTypePointerDecls()314 spirv::Blob *getSpirvTypePointerDecls() { return &mSpirvTypePointerDecls; } getSpirvFunctionTypeDecls()315 spirv::Blob *getSpirvFunctionTypeDecls() { return &mSpirvFunctionTypeDecls; } getSpirvVariableDecls()316 spirv::Blob *getSpirvVariableDecls() { return &mSpirvVariableDecls; } getSpirvFunctions()317 spirv::Blob *getSpirvFunctions() { return &mSpirvFunctions; } getSpirvCurrentFunctionBlock()318 spirv::Blob *getSpirvCurrentFunctionBlock() 319 { 320 ASSERT(!mSpirvCurrentFunctionBlocks.empty() && 321 !mSpirvCurrentFunctionBlocks.back().isTerminated); 322 return &mSpirvCurrentFunctionBlocks.back().body; 323 } getSpirvCurrentFunctionBlockId()324 spirv::IdRef getSpirvCurrentFunctionBlockId() 325 { 326 ASSERT(!mSpirvCurrentFunctionBlocks.empty() && 327 !mSpirvCurrentFunctionBlocks.back().isTerminated); 328 return mSpirvCurrentFunctionBlocks.back().labelId; 329 } isCurrentFunctionBlockTerminated()330 bool isCurrentFunctionBlockTerminated() const 331 { 332 ASSERT(!mSpirvCurrentFunctionBlocks.empty()); 333 return mSpirvCurrentFunctionBlocks.back().isTerminated; 334 } terminateCurrentFunctionBlock()335 void terminateCurrentFunctionBlock() 336 { 337 ASSERT(!mSpirvCurrentFunctionBlocks.empty()); 338 mSpirvCurrentFunctionBlocks.back().isTerminated = true; 339 } getCurrentConditional()340 const SpirvConditional *getCurrentConditional() { return &mConditionalStack.back(); } 341 342 bool isInvariantOutput(const TType &type) const; 343 344 void addCapability(spv::Capability capability); 345 void setEntryPointId(spirv::IdRef id); 346 void addEntryPointInterfaceVariableId(spirv::IdRef id); 347 void writePerVertexBuiltIns(const TType &type, spirv::IdRef typeId); 348 void writeInterfaceVariableDecorations(const TType &type, spirv::IdRef variableId); 349 void writeBranchConditional(spirv::IdRef conditionValue, 350 spirv::IdRef trueBlock, 351 spirv::IdRef falseBlock, 352 spirv::IdRef mergeBlock); 353 void writeBranchConditionalBlockEnd(); 354 void writeLoopHeader(spirv::IdRef branchToBlock, 355 spirv::IdRef continueBlock, 356 spirv::IdRef mergeBlock); 357 void writeLoopConditionEnd(spirv::IdRef conditionValue, 358 spirv::IdRef branchToBlock, 359 spirv::IdRef mergeBlock); 360 void writeLoopContinueEnd(spirv::IdRef headerBlock); 361 void writeLoopBodyEnd(spirv::IdRef continueBlock); 362 void writeSwitch(spirv::IdRef conditionValue, 363 spirv::IdRef defaultBlock, 364 const spirv::PairLiteralIntegerIdRefList &targetPairList, 365 spirv::IdRef mergeBlock); 366 void writeSwitchCaseBlockEnd(); 367 368 spirv::IdRef getBoolConstant(bool value); 369 spirv::IdRef getUintConstant(uint32_t value); 370 spirv::IdRef getIntConstant(int32_t value); 371 spirv::IdRef getFloatConstant(float value); 372 spirv::IdRef getUvecConstant(uint32_t value, int size); 373 spirv::IdRef getIvecConstant(int32_t value, int size); 374 spirv::IdRef getVecConstant(float value, int size); 375 spirv::IdRef getCompositeConstant(spirv::IdRef typeId, const spirv::IdRefList &values); 376 spirv::IdRef getNullConstant(spirv::IdRef typeId); 377 378 // Helpers to start and end a function. 379 void startNewFunction(spirv::IdRef functionId, const TFunction *func); 380 void assembleSpirvFunctionBlocks(); 381 382 // Helper to declare a variable. Function-local variables must be placed in the first block of 383 // the current function. 384 spirv::IdRef declareVariable(spirv::IdRef typeId, 385 spv::StorageClass storageClass, 386 const SpirvDecorations &decorations, 387 spirv::IdRef *initializerId, 388 const char *name); 389 // Helper to declare specialization constants. 390 spirv::IdRef declareSpecConst(TBasicType type, int id, const char *name); 391 392 // Helpers for conditionals. 393 void startConditional(size_t blockCount, bool isContinuable, bool isBreakable); 394 void nextConditionalBlock(); 395 void endConditional(); 396 bool isInLoop() const; 397 spirv::IdRef getBreakTargetId() const; 398 spirv::IdRef getContinueTargetId() const; 399 400 // TODO: remove name hashing once translation through glslang is removed. That is necessary to 401 // avoid name collision between ANGLE's internal symbols and user-defined ones when compiling 402 // the generated GLSL, but is irrelevant when generating SPIR-V directly. Currently, the SPIR-V 403 // transformer relies on the "mapped" names, which should also be changed when this hashing is 404 // removed. 405 ImmutableString hashName(const TSymbol *symbol); 406 ImmutableString hashTypeName(const TType &type); 407 ImmutableString hashFieldName(const TField *field); 408 ImmutableString hashFunctionName(const TFunction *func); 409 410 spirv::Blob getSpirv(); 411 412 private: 413 SpirvTypeData declareType(const SpirvType &type, const TSymbol *block); 414 415 uint32_t calculateBaseAlignmentAndSize(const SpirvType &type, uint32_t *sizeInStorageBlockOut); 416 uint32_t calculateSizeAndWriteOffsetDecorations(const SpirvType &type, 417 spirv::IdRef typeId, 418 uint32_t blockBaseAlignment); 419 void writeMemberDecorations(const SpirvType &type, spirv::IdRef typeId); 420 void writeInterpolationDecoration(TQualifier qualifier, spirv::IdRef id, uint32_t fieldIndex); 421 422 // Helpers for type declaration. 423 void getImageTypeParameters(TBasicType type, 424 spirv::IdRef *sampledTypeOut, 425 spv::Dim *dimOut, 426 spirv::LiteralInteger *depthOut, 427 spirv::LiteralInteger *arrayedOut, 428 spirv::LiteralInteger *multisampledOut, 429 spirv::LiteralInteger *sampledOut); 430 spv::ImageFormat getImageFormat(TLayoutImageInternalFormat imageInternalFormat); 431 432 spirv::IdRef getBasicConstantHelper(uint32_t value, 433 TBasicType type, 434 angle::HashMap<uint32_t, spirv::IdRef> *constants); 435 spirv::IdRef getNullVectorConstantHelper(TBasicType type, int size); 436 spirv::IdRef getVectorConstantHelper(spirv::IdRef valueId, TBasicType type, int size); 437 438 uint32_t nextUnusedBinding(); 439 uint32_t nextUnusedInputLocation(uint32_t consumedCount); 440 uint32_t nextUnusedOutputLocation(uint32_t consumedCount); 441 442 void generateExecutionModes(spirv::Blob *blob); 443 444 ANGLE_MAYBE_UNUSED TCompiler *mCompiler; 445 ShCompileOptions mCompileOptions; 446 gl::ShaderType mShaderType; 447 const bool mDisableRelaxedPrecision; 448 449 // Capabilities the shader is using. Accumulated as the instructions are generated. The Shader 450 // capability is unconditionally generated, so it's not tracked. 451 std::set<spv::Capability> mCapabilities; 452 453 // The list of interface variables and the id of main() populated as the instructions are 454 // generated. Used for the OpEntryPoint instruction. 455 spirv::IdRefList mEntryPointInterfaceList; 456 spirv::IdRef mEntryPointId; 457 458 // Id of imported instructions, if used. 459 spirv::IdRef mExtInstImportIdStd; 460 461 // Current ID bound, used to allocate new ids. 462 spirv::IdRef mNextAvailableId; 463 464 // A map from the AST type to the corresponding SPIR-V ID and associated data. Note that TType 465 // includes a lot of information that pertains to the variable that has the type, not the type 466 // itself. SpirvType instead contains only information that can identify the type itself. 467 angle::HashMap<SpirvType, SpirvTypeData, SpirvTypeHash> mTypeMap; 468 469 // Various sections of SPIR-V. Each section grows as SPIR-V is generated, and the final result 470 // is obtained by stitching the sections together. This puts the instructions in the order 471 // required by the spec. 472 spirv::Blob mSpirvDebug; 473 spirv::Blob mSpirvDecorations; 474 spirv::Blob mSpirvTypeAndConstantDecls; 475 spirv::Blob mSpirvTypePointerDecls; 476 spirv::Blob mSpirvFunctionTypeDecls; 477 spirv::Blob mSpirvVariableDecls; 478 spirv::Blob mSpirvFunctions; 479 // A list of blocks created for the current function. These are assembled by 480 // assembleSpirvFunctionBlocks() when the function is entirely visited. Local variables need to 481 // be inserted at the beginning of the first function block, so the entire SPIR-V of the 482 // function cannot be obtained until it's fully visited. 483 // 484 // The last block in this list is the one currently being written to. 485 std::vector<SpirvBlock> mSpirvCurrentFunctionBlocks; 486 487 // List of constants that are already defined (for reuse). 488 spirv::IdRef mBoolConstants[2]; 489 angle::HashMap<uint32_t, spirv::IdRef> mUintConstants; 490 angle::HashMap<uint32_t, spirv::IdRef> mIntConstants; 491 angle::HashMap<uint32_t, spirv::IdRef> mFloatConstants; 492 angle::HashMap<SpirvIdAndIdList, spirv::IdRef, SpirvIdAndIdListHash> mCompositeConstants; 493 // Keyed by typeId, returns the null constant corresponding to that type. 494 std::vector<spirv::IdRef> mNullConstants; 495 496 // List of type pointers that are already defined. 497 // TODO: if all users call getTypeData(), move to SpirvTypeData. http://anglebug.com/4889 498 angle::HashMap<SpirvIdAndStorageClass, spirv::IdRef, SpirvIdAndStorageClassHash> 499 mTypePointerIdMap; 500 501 // List of function types that are already defined. 502 angle::HashMap<SpirvIdAndIdList, spirv::IdRef, SpirvIdAndIdListHash> mFunctionTypeIdMap; 503 504 // Stack of conditionals. When an if, loop or switch is visited, a new conditional scope is 505 // added. When the conditional construct is entirely visited, it's popped. As the blocks of 506 // the conditional constructs are visited, ids are consumed from the top of the stack. When 507 // break or continue is visited, the stack is traversed backwards until a loop or switch is 508 // found. 509 std::vector<SpirvConditional> mConditionalStack; 510 511 // name hashing. 512 ShHashFunction64 mHashFunction; 513 NameMap &mNameMap; 514 515 // Every resource that requires set & binding layout qualifiers is assigned set 0 and an 516 // arbitrary binding. Every input/output that requires a location layout qualifier is assigned 517 // an arbitrary location as well. 518 // 519 // The link-time SPIR-V transformer modifies set, binding and location decorations in SPIR-V 520 // directly. 521 uint32_t mNextUnusedBinding; 522 uint32_t mNextUnusedInputLocation; 523 uint32_t mNextUnusedOutputLocation; 524 }; 525 } // namespace sh 526 527 #endif // COMPILER_TRANSLATOR_BUILDSPIRV_H_ 528