| // |
| // Copyright 2021 The ANGLE Project Authors. All rights reserved. |
| // Use of this source code is governed by a BSD-style license that can be |
| // found in the LICENSE file. |
| // |
| // BuildSPIRV: Helper for OutputSPIRV to build SPIR-V. |
| // |
| |
| #include "compiler/translator/BuildSPIRV.h" |
| |
| #include "common/spirv/spirv_instruction_builder_autogen.h" |
| #include "compiler/translator/ValidateVaryingLocations.h" |
| #include "compiler/translator/blocklayout.h" |
| #include "compiler/translator/util.h" |
| |
| namespace sh |
| { |
| bool operator==(const SpirvType &a, const SpirvType &b) |
| { |
| if (a.block != b.block) |
| { |
| return false; |
| } |
| |
| if (a.arraySizes != b.arraySizes) |
| { |
| return false; |
| } |
| |
| // If structure or interface block, they should match by pointer (i.e. be the same block). The |
| // AST transformations are expected to keep the AST consistent by using the same structure and |
| // interface block pointer between declarations and usages. This is validated by |
| // ValidateASTOptions::validateVariableReferences. |
| if (a.block != nullptr) |
| { |
| return a.typeSpec.blockStorage == b.typeSpec.blockStorage && |
| a.typeSpec.isInvariantBlock == b.typeSpec.isInvariantBlock && |
| a.typeSpec.isRowMajorQualifiedBlock == b.typeSpec.isRowMajorQualifiedBlock && |
| a.typeSpec.isOrHasBoolInInterfaceBlock == b.typeSpec.isOrHasBoolInInterfaceBlock; |
| } |
| |
| // Otherwise, match by the type contents. The AST transformations sometimes recreate types that |
| // are already defined, so we can't rely on pointers being unique. |
| return a.type == b.type && a.primarySize == b.primarySize && |
| a.secondarySize == b.secondarySize && a.imageInternalFormat == b.imageInternalFormat && |
| a.isSamplerBaseImage == b.isSamplerBaseImage && |
| a.typeSpec.blockStorage == b.typeSpec.blockStorage && |
| a.typeSpec.isRowMajorQualifiedArray == b.typeSpec.isRowMajorQualifiedArray && |
| a.typeSpec.isOrHasBoolInInterfaceBlock == b.typeSpec.isOrHasBoolInInterfaceBlock; |
| } |
| |
| namespace |
| { |
| bool IsBlockFieldRowMajorQualified(const TType &fieldType, bool isParentBlockRowMajorQualified) |
| { |
| // If the field is specifically qualified as row-major, it will be row-major. Otherwise unless |
| // specifically qualified as column-major, its matrix packing is inherited from the parent |
| // block. |
| const TLayoutMatrixPacking fieldMatrixPacking = fieldType.getLayoutQualifier().matrixPacking; |
| return fieldMatrixPacking == EmpRowMajor || |
| (fieldMatrixPacking == EmpUnspecified && isParentBlockRowMajorQualified); |
| } |
| |
| bool IsNonSquareRowMajorArrayInBlock(const TType &type, const SpirvTypeSpec &parentTypeSpec) |
| { |
| return parentTypeSpec.blockStorage != EbsUnspecified && type.isArray() && type.isMatrix() && |
| type.getCols() != type.getRows() && |
| IsBlockFieldRowMajorQualified(type, parentTypeSpec.isRowMajorQualifiedBlock); |
| } |
| |
| bool IsInvariant(const TType &type, TCompiler *compiler) |
| { |
| const bool invariantAll = compiler->getPragma().stdgl.invariantAll; |
| |
| // The Invariant decoration is applied to output variables if specified or if globally enabled. |
| return type.isInvariant() || (IsShaderOut(type.getQualifier()) && invariantAll); |
| } |
| |
| TLayoutBlockStorage GetBlockStorage(const TType &type) |
| { |
| // For interface blocks, the block storage is specified on the symbol itself. |
| if (type.getInterfaceBlock() != nullptr) |
| { |
| return type.getInterfaceBlock()->blockStorage(); |
| } |
| |
| // I/O blocks must have been handled above. |
| ASSERT(!IsShaderIoBlock(type.getQualifier())); |
| |
| // Additionally, interface blocks are already handled, so it's not expected for the type to have |
| // a block storage specified. |
| ASSERT(type.getLayoutQualifier().blockStorage == EbsUnspecified); |
| |
| // Default to std140 for uniform and std430 for buffer blocks. |
| return type.getQualifier() == EvqBuffer ? EbsStd430 : EbsStd140; |
| } |
| |
| ShaderVariable ToShaderVariable(const TFieldListCollection *block, |
| GLenum type, |
| const TSpan<const unsigned int> arraySizes, |
| bool isRowMajor) |
| { |
| ShaderVariable var; |
| |
| var.type = type; |
| var.arraySizes = {arraySizes.begin(), arraySizes.end()}; |
| var.isRowMajorLayout = isRowMajor; |
| |
| if (block != nullptr) |
| { |
| for (const TField *field : block->fields()) |
| { |
| const TType &fieldType = *field->type(); |
| |
| const TLayoutMatrixPacking fieldMatrixPacking = |
| fieldType.getLayoutQualifier().matrixPacking; |
| const bool isFieldRowMajor = fieldMatrixPacking == EmpRowMajor || |
| (fieldMatrixPacking == EmpUnspecified && isRowMajor); |
| const GLenum glType = |
| fieldType.getStruct() != nullptr ? GL_NONE : GLVariableType(fieldType); |
| |
| var.fields.push_back(ToShaderVariable(fieldType.getStruct(), glType, |
| fieldType.getArraySizes(), isFieldRowMajor)); |
| } |
| } |
| |
| return var; |
| } |
| |
| ShaderVariable SpirvTypeToShaderVariable(const SpirvType &type) |
| { |
| const bool isRowMajor = |
| type.typeSpec.isRowMajorQualifiedBlock || type.typeSpec.isRowMajorQualifiedArray; |
| const GLenum glType = |
| type.block != nullptr |
| ? EbtStruct |
| : GLVariableType(TType(type.type, type.primarySize, type.secondarySize)); |
| |
| return ToShaderVariable(type.block, glType, type.arraySizes, isRowMajor); |
| } |
| |
| // The following function encodes a variable in a std140 or std430 block. The variable could be: |
| // |
| // - An interface block: In this case, |decorationsBlob| is provided and SPIR-V decorations are |
| // output to this blob. |
| // - A struct: In this case, the return value is of interest as the size of the struct in the |
| // encoding. |
| // |
| // This function ignores arrayness in calculating the struct size. |
| // |
| uint32_t Encode(const ShaderVariable &var, |
| bool isStd140, |
| spirv::IdRef blockTypeId, |
| spirv::Blob *decorationsBlob) |
| { |
| Std140BlockEncoder std140; |
| Std430BlockEncoder std430; |
| BlockLayoutEncoder *encoder = isStd140 ? &std140 : &std430; |
| |
| ASSERT(var.isStruct()); |
| encoder->enterAggregateType(var); |
| |
| uint32_t fieldIndex = 0; |
| |
| for (const ShaderVariable &fieldVar : var.fields) |
| { |
| BlockMemberInfo fieldInfo; |
| |
| // Encode the variable. |
| if (fieldVar.isStruct()) |
| { |
| // For structs, recursively encode it. |
| const uint32_t structSize = Encode(fieldVar, isStd140, {}, nullptr); |
| |
| encoder->enterAggregateType(fieldVar); |
| fieldInfo = encoder->encodeArrayOfPreEncodedStructs(structSize, fieldVar.arraySizes); |
| encoder->exitAggregateType(fieldVar); |
| } |
| else |
| { |
| fieldInfo = |
| encoder->encodeType(fieldVar.type, fieldVar.arraySizes, fieldVar.isRowMajorLayout); |
| } |
| |
| if (decorationsBlob) |
| { |
| ASSERT(blockTypeId.valid()); |
| |
| // Write the Offset decoration. |
| spirv::WriteMemberDecorate(decorationsBlob, blockTypeId, |
| spirv::LiteralInteger(fieldIndex), spv::DecorationOffset, |
| {spirv::LiteralInteger(fieldInfo.offset)}); |
| |
| // For matrix types, write the MatrixStride decoration as well. |
| if (IsMatrixGLType(fieldVar.type)) |
| { |
| ASSERT(fieldInfo.matrixStride > 0); |
| |
| // MatrixStride |
| spirv::WriteMemberDecorate( |
| decorationsBlob, blockTypeId, spirv::LiteralInteger(fieldIndex), |
| spv::DecorationMatrixStride, {spirv::LiteralInteger(fieldInfo.matrixStride)}); |
| } |
| } |
| |
| ++fieldIndex; |
| } |
| |
| encoder->exitAggregateType(var); |
| return static_cast<uint32_t>(encoder->getCurrentOffset()); |
| } |
| |
| uint32_t GetArrayStrideInBlock(const ShaderVariable &var, bool isStd140) |
| { |
| Std140BlockEncoder std140; |
| Std430BlockEncoder std430; |
| BlockLayoutEncoder *encoder = isStd140 ? &std140 : &std430; |
| |
| ASSERT(var.isArray()); |
| |
| // For structs, encode the struct to get the size, and calculate the stride based on that. |
| if (var.isStruct()) |
| { |
| // Remove arrayness. |
| ShaderVariable element = var; |
| element.arraySizes.clear(); |
| |
| const uint32_t structSize = Encode(element, isStd140, {}, nullptr); |
| |
| // Stride is struct size by inner array size |
| return structSize * var.getInnerArraySizeProduct(); |
| } |
| |
| // Otherwise encode the basic type. |
| BlockMemberInfo memberInfo = |
| encoder->encodeType(var.type, var.arraySizes, var.isRowMajorLayout); |
| |
| // The encoder returns the array stride for the base element type (which is not an array!), so |
| // need to multiply by the inner array sizes to get the outermost array's stride. |
| return memberInfo.arrayStride * var.getInnerArraySizeProduct(); |
| } |
| |
| spv::ExecutionMode GetGeometryInputExecutionMode(TLayoutPrimitiveType primitiveType) |
| { |
| // Default input primitive type for geometry shaders is points |
| if (primitiveType == EptUndefined) |
| { |
| primitiveType = EptPoints; |
| } |
| |
| switch (primitiveType) |
| { |
| case EptPoints: |
| return spv::ExecutionModeInputPoints; |
| case EptLines: |
| return spv::ExecutionModeInputLines; |
| case EptLinesAdjacency: |
| return spv::ExecutionModeInputLinesAdjacency; |
| case EptTriangles: |
| return spv::ExecutionModeTriangles; |
| case EptTrianglesAdjacency: |
| return spv::ExecutionModeInputTrianglesAdjacency; |
| case EptLineStrip: |
| case EptTriangleStrip: |
| default: |
| UNREACHABLE(); |
| return {}; |
| } |
| } |
| |
| spv::ExecutionMode GetGeometryOutputExecutionMode(TLayoutPrimitiveType primitiveType) |
| { |
| // Default output primitive type for geometry shaders is points |
| if (primitiveType == EptUndefined) |
| { |
| primitiveType = EptPoints; |
| } |
| |
| switch (primitiveType) |
| { |
| case EptPoints: |
| return spv::ExecutionModeOutputPoints; |
| case EptLineStrip: |
| return spv::ExecutionModeOutputLineStrip; |
| case EptTriangleStrip: |
| return spv::ExecutionModeOutputTriangleStrip; |
| case EptLines: |
| case EptLinesAdjacency: |
| case EptTriangles: |
| case EptTrianglesAdjacency: |
| default: |
| UNREACHABLE(); |
| return {}; |
| } |
| } |
| |
| spv::ExecutionMode GetTessEvalInputExecutionMode(TLayoutTessEvaluationType inputType) |
| { |
| switch (inputType) |
| { |
| case EtetTriangles: |
| return spv::ExecutionModeTriangles; |
| case EtetQuads: |
| return spv::ExecutionModeQuads; |
| case EtetIsolines: |
| return spv::ExecutionModeIsolines; |
| default: |
| UNREACHABLE(); |
| return {}; |
| } |
| } |
| |
| spv::ExecutionMode GetTessEvalSpacingExecutionMode(TLayoutTessEvaluationType spacing) |
| { |
| switch (spacing) |
| { |
| case EtetEqualSpacing: |
| case EtetUndefined: |
| return spv::ExecutionModeSpacingEqual; |
| case EtetFractionalEvenSpacing: |
| return spv::ExecutionModeSpacingFractionalEven; |
| case EtetFractionalOddSpacing: |
| return spv::ExecutionModeSpacingFractionalOdd; |
| default: |
| UNREACHABLE(); |
| return {}; |
| } |
| } |
| |
| spv::ExecutionMode GetTessEvalOrderingExecutionMode(TLayoutTessEvaluationType ordering) |
| { |
| switch (ordering) |
| { |
| case EtetCw: |
| return spv::ExecutionModeVertexOrderCw; |
| case EtetCcw: |
| case EtetUndefined: |
| return spv::ExecutionModeVertexOrderCcw; |
| default: |
| UNREACHABLE(); |
| return {}; |
| } |
| } |
| } // anonymous namespace |
| |
| void SpirvTypeSpec::inferDefaults(const TType &type, TCompiler *compiler) |
| { |
| // Infer some defaults based on type. If necessary, this overrides some fields (if not already |
| // specified). Otherwise, it leaves the pre-initialized values as-is. |
| |
| // Handle interface blocks and fields of nameless interface blocks. |
| if (type.getInterfaceBlock() != nullptr) |
| { |
| // Calculate the block storage from the interface block automatically. The fields inherit |
| // from this. Only blocks and arrays in blocks produce different SPIR-V types based on |
| // block storage. |
| const bool isBlock = type.isInterfaceBlock() || type.getStruct(); |
| if (blockStorage == EbsUnspecified && (isBlock || type.isArray())) |
| { |
| blockStorage = GetBlockStorage(type); |
| } |
| |
| // row_major can only be specified on an interface block or one of its fields. The fields |
| // will inherit this from the interface block itself. |
| if (!isRowMajorQualifiedBlock && isBlock) |
| { |
| isRowMajorQualifiedBlock = type.getLayoutQualifier().matrixPacking == EmpRowMajor; |
| } |
| |
| // Arrays of matrices in a uniform/buffer block may generate a different stride based on |
| // whether they are row- or column-major. Square matrices are trivially known not to |
| // generate a different type. |
| if (!isRowMajorQualifiedArray) |
| { |
| isRowMajorQualifiedArray = IsNonSquareRowMajorArrayInBlock(type, *this); |
| } |
| |
| // Structs with bools, bool arrays, bool vectors and bools themselves are replaced with uint |
| // when used in an interface block. |
| if (!isOrHasBoolInInterfaceBlock) |
| { |
| isOrHasBoolInInterfaceBlock = type.isInterfaceBlockContainingType(EbtBool) || |
| type.isStructureContainingType(EbtBool) || |
| type.getBasicType() == EbtBool; |
| } |
| } |
| |
| // |invariant| is significant for structs as the fields of the type are decorated with Invariant |
| // in SPIR-V. This is possible for outputs of struct type, or struct-typed fields of an |
| // interface block. |
| if (type.getStruct() != nullptr) |
| { |
| isInvariantBlock = isInvariantBlock || IsInvariant(type, compiler); |
| } |
| } |
| |
| void SpirvTypeSpec::onArrayElementSelection(bool isElementTypeBlock, bool isElementTypeArray) |
| { |
| // No difference in type for non-block non-array types in std140 and std430 block storage. |
| if (!isElementTypeBlock && !isElementTypeArray) |
| { |
| blockStorage = EbsUnspecified; |
| } |
| |
| // No difference in type for non-array types in std140 and std430 block storage. |
| if (!isElementTypeArray) |
| { |
| isRowMajorQualifiedArray = false; |
| } |
| } |
| |
| void SpirvTypeSpec::onBlockFieldSelection(const TType &fieldType) |
| { |
| if (fieldType.getStruct() == nullptr) |
| { |
| // If the field is not a block, no difference if the parent block was invariant or |
| // row-major. |
| isRowMajorQualifiedArray = IsNonSquareRowMajorArrayInBlock(fieldType, *this); |
| isInvariantBlock = false; |
| isRowMajorQualifiedBlock = false; |
| |
| // If the field is not an array, no difference in storage block. |
| if (!fieldType.isArray()) |
| { |
| blockStorage = EbsUnspecified; |
| } |
| |
| if (fieldType.getBasicType() != EbtBool) |
| { |
| isOrHasBoolInInterfaceBlock = false; |
| } |
| } |
| else |
| { |
| // Apply row-major only to structs that contain matrices. |
| isRowMajorQualifiedBlock = |
| IsBlockFieldRowMajorQualified(fieldType, isRowMajorQualifiedBlock) && |
| fieldType.isStructureContainingMatrices(); |
| |
| // Structs without bools aren't affected by |isOrHasBoolInInterfaceBlock|. |
| if (isOrHasBoolInInterfaceBlock) |
| { |
| isOrHasBoolInInterfaceBlock = fieldType.isStructureContainingType(EbtBool); |
| } |
| } |
| } |
| |
| void SpirvTypeSpec::onMatrixColumnSelection() |
| { |
| // The matrix types are never differentiated, so neither would be their columns. |
| ASSERT(!isInvariantBlock && !isRowMajorQualifiedBlock && !isRowMajorQualifiedArray && |
| !isOrHasBoolInInterfaceBlock && blockStorage == EbsUnspecified); |
| } |
| |
| void SpirvTypeSpec::onVectorComponentSelection() |
| { |
| // The vector types are never differentiated, so neither would be their components. The only |
| // exception is bools in interface blocks, in which case the component and the vector are |
| // similarly differentiated. |
| ASSERT(!isInvariantBlock && !isRowMajorQualifiedBlock && !isRowMajorQualifiedArray && |
| blockStorage == EbsUnspecified); |
| } |
| |
| SPIRVBuilder::SPIRVBuilder(TCompiler *compiler, |
| ShCompileOptions compileOptions, |
| bool forceHighp, |
| ShHashFunction64 hashFunction, |
| NameMap &nameMap) |
| : mCompiler(compiler), |
| mCompileOptions(compileOptions), |
| mShaderType(gl::FromGLenum<gl::ShaderType>(compiler->getShaderType())), |
| mDisableRelaxedPrecision(forceHighp), |
| mNextAvailableId(1), |
| mHashFunction(hashFunction), |
| mNameMap(nameMap), |
| mNextUnusedBinding(0), |
| mNextUnusedInputLocation(0), |
| mNextUnusedOutputLocation(0) |
| { |
| // The Shader capability is always defined. |
| addCapability(spv::CapabilityShader); |
| |
| // Add Geometry or Tessellation capabilities based on shader type. |
| if (mCompiler->getShaderType() == GL_GEOMETRY_SHADER) |
| { |
| addCapability(spv::CapabilityGeometry); |
| } |
| else if (mCompiler->getShaderType() == GL_TESS_CONTROL_SHADER_EXT || |
| mCompiler->getShaderType() == GL_TESS_EVALUATION_SHADER_EXT) |
| { |
| addCapability(spv::CapabilityTessellation); |
| } |
| } |
| |
| spirv::IdRef SPIRVBuilder::getNewId(const SpirvDecorations &decorations) |
| { |
| spirv::IdRef newId = mNextAvailableId; |
| mNextAvailableId = spirv::IdRef(mNextAvailableId + 1); |
| |
| for (const spv::Decoration decoration : decorations) |
| { |
| spirv::WriteDecorate(&mSpirvDecorations, newId, decoration, {}); |
| } |
| |
| return newId; |
| } |
| |
| SpirvType SPIRVBuilder::getSpirvType(const TType &type, const SpirvTypeSpec &typeSpec) const |
| { |
| SpirvType spirvType; |
| spirvType.type = type.getBasicType(); |
| spirvType.primarySize = static_cast<uint8_t>(type.getNominalSize()); |
| spirvType.secondarySize = static_cast<uint8_t>(type.getSecondarySize()); |
| spirvType.arraySizes = type.getArraySizes(); |
| spirvType.imageInternalFormat = type.getLayoutQualifier().imageInternalFormat; |
| |
| switch (spirvType.type) |
| { |
| // External textures are treated as 2D textures in the vulkan back-end. |
| case EbtSamplerExternalOES: |
| case EbtSamplerExternal2DY2YEXT: |
| // WEBGL video textures too. |
| case EbtSamplerVideoWEBGL: |
| spirvType.type = EbtSampler2D; |
| break; |
| default: |
| break; |
| } |
| |
| if (type.getStruct() != nullptr) |
| { |
| spirvType.block = type.getStruct(); |
| } |
| else if (type.isInterfaceBlock()) |
| { |
| spirvType.block = type.getInterfaceBlock(); |
| } |
| |
| // Automatically inherit or infer the type-specializing properties. |
| spirvType.typeSpec = typeSpec; |
| spirvType.typeSpec.inferDefaults(type, mCompiler); |
| |
| return spirvType; |
| } |
| |
| const SpirvTypeData &SPIRVBuilder::getTypeData(const TType &type, const SpirvTypeSpec &typeSpec) |
| { |
| SpirvType spirvType = getSpirvType(type, typeSpec); |
| |
| const TSymbol *block = nullptr; |
| if (type.getStruct() != nullptr) |
| { |
| block = type.getStruct(); |
| } |
| else if (type.isInterfaceBlock()) |
| { |
| block = type.getInterfaceBlock(); |
| } |
| |
| return getSpirvTypeData(spirvType, block); |
| } |
| |
| const SpirvTypeData &SPIRVBuilder::getSpirvTypeData(const SpirvType &type, const TSymbol *block) |
| { |
| // Structs with bools generate a different type when used in an interface block (where the bool |
| // is replaced with a uint). The bool, bool vector and bool arrays too generate a different |
| // type when nested in an interface block, but that type is the same as the equivalent uint |
| // type. To avoid defining duplicate uint types, we switch the basic type here to uint. From |
| // the outside, therefore bool in an interface block and uint look like different types, but |
| // under the hood will be the same uint. |
| if (type.block == nullptr && type.typeSpec.isOrHasBoolInInterfaceBlock) |
| { |
| ASSERT(type.type == EbtBool); |
| |
| SpirvType uintType = type; |
| uintType.type = EbtUInt; |
| uintType.typeSpec.isOrHasBoolInInterfaceBlock = false; |
| return getSpirvTypeData(uintType, block); |
| } |
| |
| auto iter = mTypeMap.find(type); |
| if (iter == mTypeMap.end()) |
| { |
| SpirvTypeData newTypeData = declareType(type, block); |
| |
| iter = mTypeMap.insert({type, newTypeData}).first; |
| } |
| |
| return iter->second; |
| } |
| |
| spirv::IdRef SPIRVBuilder::getBasicTypeId(TBasicType basicType, size_t size) |
| { |
| SpirvType type; |
| type.type = basicType; |
| type.primarySize = static_cast<uint8_t>(size); |
| return getSpirvTypeData(type, nullptr).id; |
| } |
| |
| spirv::IdRef SPIRVBuilder::getTypePointerId(spirv::IdRef typeId, spv::StorageClass storageClass) |
| { |
| SpirvIdAndStorageClass key{typeId, storageClass}; |
| |
| auto iter = mTypePointerIdMap.find(key); |
| if (iter == mTypePointerIdMap.end()) |
| { |
| const spirv::IdRef typePointerId = getNewId({}); |
| |
| spirv::WriteTypePointer(&mSpirvTypePointerDecls, typePointerId, storageClass, typeId); |
| |
| iter = mTypePointerIdMap.insert({key, typePointerId}).first; |
| } |
| |
| return iter->second; |
| } |
| |
| spirv::IdRef SPIRVBuilder::getFunctionTypeId(spirv::IdRef returnTypeId, |
| const spirv::IdRefList ¶mTypeIds) |
| { |
| SpirvIdAndIdList key{returnTypeId, paramTypeIds}; |
| |
| auto iter = mFunctionTypeIdMap.find(key); |
| if (iter == mFunctionTypeIdMap.end()) |
| { |
| const spirv::IdRef functionTypeId = getNewId({}); |
| |
| spirv::WriteTypeFunction(&mSpirvFunctionTypeDecls, functionTypeId, returnTypeId, |
| paramTypeIds); |
| |
| iter = mFunctionTypeIdMap.insert({key, functionTypeId}).first; |
| } |
| |
| return iter->second; |
| } |
| |
| SpirvDecorations SPIRVBuilder::getDecorations(const TType &type) |
| { |
| const bool enablePrecision = (mCompileOptions & SH_IGNORE_PRECISION_QUALIFIERS) == 0; |
| const TPrecision precision = type.getPrecision(); |
| |
| SpirvDecorations decorations; |
| |
| // Handle precision. |
| if (enablePrecision && !mDisableRelaxedPrecision && |
| (precision == EbpMedium || precision == EbpLow)) |
| { |
| decorations.push_back(spv::DecorationRelaxedPrecision); |
| } |
| |
| // TODO: Handle |precise|. http://anglebug.com/4889. |
| |
| return decorations; |
| } |
| |
| spirv::IdRef SPIRVBuilder::getExtInstImportIdStd() |
| { |
| if (!mExtInstImportIdStd.valid()) |
| { |
| mExtInstImportIdStd = getNewId({}); |
| } |
| return mExtInstImportIdStd; |
| } |
| |
| SpirvTypeData SPIRVBuilder::declareType(const SpirvType &type, const TSymbol *block) |
| { |
| // Recursively declare the type. Type id is allocated afterwards purely for better id order in |
| // output. |
| spirv::IdRef typeId; |
| |
| if (!type.arraySizes.empty()) |
| { |
| // Declaring an array. First, declare the type without the outermost array size, then |
| // declare a new array type based on that. |
| |
| SpirvType subType = type; |
| subType.arraySizes = type.arraySizes.first(type.arraySizes.size() - 1); |
| subType.typeSpec.onArrayElementSelection(subType.block != nullptr, |
| !subType.arraySizes.empty()); |
| |
| const spirv::IdRef subTypeId = getSpirvTypeData(subType, block).id; |
| |
| const unsigned int length = type.arraySizes.back(); |
| |
| if (length == 0) |
| { |
| // Storage buffers may include a dynamically-sized array, which is identified by it |
| // having a length of 0. |
| typeId = getNewId({}); |
| spirv::WriteTypeRuntimeArray(&mSpirvTypeAndConstantDecls, typeId, subTypeId); |
| } |
| else |
| { |
| const spirv::IdRef lengthId = getUintConstant(length); |
| typeId = getNewId({}); |
| spirv::WriteTypeArray(&mSpirvTypeAndConstantDecls, typeId, subTypeId, lengthId); |
| } |
| } |
| else if (type.block != nullptr) |
| { |
| // Declaring a block. First, declare all the fields, then declare a struct based on the |
| // list of field types. |
| |
| spirv::IdRefList fieldTypeIds; |
| for (const TField *field : type.block->fields()) |
| { |
| const TType &fieldType = *field->type(); |
| |
| SpirvTypeSpec fieldTypeSpec = type.typeSpec; |
| fieldTypeSpec.onBlockFieldSelection(fieldType); |
| |
| const SpirvType fieldSpirvType = getSpirvType(fieldType, fieldTypeSpec); |
| const spirv::IdRef fieldTypeId = |
| getSpirvTypeData(fieldSpirvType, fieldType.getStruct()).id; |
| fieldTypeIds.push_back(fieldTypeId); |
| } |
| |
| typeId = getNewId({}); |
| spirv::WriteTypeStruct(&mSpirvTypeAndConstantDecls, typeId, fieldTypeIds); |
| } |
| else if (IsSampler(type.type) && !type.isSamplerBaseImage) |
| { |
| // Declaring a sampler. First, declare the non-sampled image and then a combined |
| // image-sampler. |
| |
| SpirvType imageType = type; |
| imageType.isSamplerBaseImage = true; |
| |
| const spirv::IdRef nonSampledId = getSpirvTypeData(imageType, nullptr).id; |
| |
| typeId = getNewId({}); |
| spirv::WriteTypeSampledImage(&mSpirvTypeAndConstantDecls, typeId, nonSampledId); |
| } |
| else if (IsImage(type.type) || IsSubpassInputType(type.type) || type.isSamplerBaseImage) |
| { |
| // Declaring an image. |
| |
| spirv::IdRef sampledType; |
| spv::Dim dim; |
| spirv::LiteralInteger depth; |
| spirv::LiteralInteger arrayed; |
| spirv::LiteralInteger multisampled; |
| spirv::LiteralInteger sampled; |
| |
| getImageTypeParameters(type.type, &sampledType, &dim, &depth, &arrayed, &multisampled, |
| &sampled); |
| const spv::ImageFormat imageFormat = getImageFormat(type.imageInternalFormat); |
| |
| typeId = getNewId({}); |
| spirv::WriteTypeImage(&mSpirvTypeAndConstantDecls, typeId, sampledType, dim, depth, arrayed, |
| multisampled, sampled, imageFormat, nullptr); |
| } |
| else if (type.secondarySize > 1) |
| { |
| // Declaring a matrix. Declare the column type first, then create a matrix out of it. |
| |
| SpirvType columnType = type; |
| columnType.primarySize = columnType.secondarySize; |
| columnType.secondarySize = 1; |
| columnType.typeSpec.onMatrixColumnSelection(); |
| |
| const spirv::IdRef columnTypeId = getSpirvTypeData(columnType, nullptr).id; |
| |
| typeId = getNewId({}); |
| spirv::WriteTypeMatrix(&mSpirvTypeAndConstantDecls, typeId, columnTypeId, |
| spirv::LiteralInteger(type.primarySize)); |
| } |
| else if (type.primarySize > 1) |
| { |
| // Declaring a vector. Declare the component type first, then create a vector out of it. |
| |
| SpirvType componentType = type; |
| componentType.primarySize = 1; |
| componentType.typeSpec.onVectorComponentSelection(); |
| |
| const spirv::IdRef componentTypeId = getSpirvTypeData(componentType, nullptr).id; |
| |
| typeId = getNewId({}); |
| spirv::WriteTypeVector(&mSpirvTypeAndConstantDecls, typeId, componentTypeId, |
| spirv::LiteralInteger(type.primarySize)); |
| } |
| else |
| { |
| typeId = getNewId({}); |
| |
| // Declaring a basic type. There's a different instruction for each. |
| switch (type.type) |
| { |
| case EbtVoid: |
| spirv::WriteTypeVoid(&mSpirvTypeAndConstantDecls, typeId); |
| break; |
| case EbtFloat: |
| spirv::WriteTypeFloat(&mSpirvTypeAndConstantDecls, typeId, |
| spirv::LiteralInteger(32)); |
| break; |
| case EbtDouble: |
| // TODO: support desktop GLSL. http://anglebug.com/6197 |
| UNIMPLEMENTED(); |
| break; |
| case EbtInt: |
| spirv::WriteTypeInt(&mSpirvTypeAndConstantDecls, typeId, spirv::LiteralInteger(32), |
| spirv::LiteralInteger(1)); |
| break; |
| case EbtUInt: |
| spirv::WriteTypeInt(&mSpirvTypeAndConstantDecls, typeId, spirv::LiteralInteger(32), |
| spirv::LiteralInteger(0)); |
| break; |
| case EbtBool: |
| spirv::WriteTypeBool(&mSpirvTypeAndConstantDecls, typeId); |
| break; |
| default: |
| UNREACHABLE(); |
| } |
| } |
| |
| // If this was a block declaration, add debug information for its type and field names. |
| // |
| // TODO: make this conditional to a compiler flag. Instead of outputting the debug info |
| // unconditionally and having the SPIR-V transformer remove them, it's better to avoid |
| // generating them in the first place. This both simplifies the transformer and reduces SPIR-V |
| // binary size that gets written to disk cache. http://anglebug.com/4889 |
| if (block != nullptr && type.arraySizes.empty()) |
| { |
| spirv::WriteName(&mSpirvDebug, typeId, hashName(block).data()); |
| |
| uint32_t fieldIndex = 0; |
| for (const TField *field : type.block->fields()) |
| { |
| spirv::WriteMemberName(&mSpirvDebug, typeId, spirv::LiteralInteger(fieldIndex++), |
| hashFieldName(field).data()); |
| } |
| } |
| |
| // Write decorations for interface block fields. |
| if (type.typeSpec.blockStorage != EbsUnspecified) |
| { |
| // Cannot have opaque uniforms inside interface blocks. |
| ASSERT(!IsOpaqueType(type.type)); |
| |
| const bool isInterfaceBlock = block != nullptr && block->isInterfaceBlock(); |
| const bool isStd140 = type.typeSpec.blockStorage != EbsStd430; |
| |
| if (!type.arraySizes.empty() && !isInterfaceBlock) |
| { |
| // Write the ArrayStride decoration for arrays inside interface blocks. An array of |
| // interface blocks doesn't need a stride. |
| const ShaderVariable var = SpirvTypeToShaderVariable(type); |
| const uint32_t stride = GetArrayStrideInBlock(var, isStd140); |
| |
| spirv::WriteDecorate(&mSpirvDecorations, typeId, spv::DecorationArrayStride, |
| {spirv::LiteralInteger(stride)}); |
| } |
| else if (type.arraySizes.empty() && type.block != nullptr) |
| { |
| // Write the Offset decoration for interface blocks and structs in them. |
| const ShaderVariable var = SpirvTypeToShaderVariable(type); |
| Encode(var, isStd140, typeId, &mSpirvDecorations); |
| } |
| } |
| |
| // Write other member decorations. |
| if (block != nullptr && type.arraySizes.empty()) |
| { |
| writeMemberDecorations(type, typeId); |
| } |
| |
| return {typeId}; |
| } |
| |
| void SPIRVBuilder::getImageTypeParameters(TBasicType type, |
| spirv::IdRef *sampledTypeOut, |
| spv::Dim *dimOut, |
| spirv::LiteralInteger *depthOut, |
| spirv::LiteralInteger *arrayedOut, |
| spirv::LiteralInteger *multisampledOut, |
| spirv::LiteralInteger *sampledOut) |
| { |
| TBasicType sampledType = EbtFloat; |
| *dimOut = IsSubpassInputType(type) ? spv::DimSubpassData : spv::Dim2D; |
| bool isDepth = false; |
| bool isArrayed = false; |
| bool isMultisampled = false; |
| |
| // Decompose the basic type into image properties |
| switch (type) |
| { |
| // Float 2D Images |
| case EbtSampler2D: |
| case EbtImage2D: |
| case EbtSubpassInput: |
| break; |
| case EbtSamplerExternalOES: |
| case EbtSamplerExternal2DY2YEXT: |
| case EbtSamplerVideoWEBGL: |
| // These must have already been converted to EbtSampler2D. |
| UNREACHABLE(); |
| break; |
| case EbtSampler2DArray: |
| case EbtImage2DArray: |
| isArrayed = true; |
| break; |
| case EbtSampler2DMS: |
| case EbtImage2DMS: |
| case EbtSubpassInputMS: |
| isMultisampled = true; |
| break; |
| case EbtSampler2DMSArray: |
| case EbtImage2DMSArray: |
| isArrayed = true; |
| isMultisampled = true; |
| break; |
| case EbtSampler2DShadow: |
| isDepth = true; |
| break; |
| case EbtSampler2DArrayShadow: |
| isDepth = true; |
| isArrayed = true; |
| break; |
| |
| // Integer 2D images |
| case EbtISampler2D: |
| case EbtIImage2D: |
| case EbtISubpassInput: |
| sampledType = EbtInt; |
| break; |
| case EbtISampler2DArray: |
| case EbtIImage2DArray: |
| sampledType = EbtInt; |
| isArrayed = true; |
| break; |
| case EbtISampler2DMS: |
| case EbtIImage2DMS: |
| case EbtISubpassInputMS: |
| sampledType = EbtInt; |
| isMultisampled = true; |
| break; |
| case EbtISampler2DMSArray: |
| case EbtIImage2DMSArray: |
| sampledType = EbtInt; |
| isArrayed = true; |
| isMultisampled = true; |
| break; |
| |
| // Unsinged integer 2D images |
| case EbtUSampler2D: |
| case EbtUImage2D: |
| case EbtUSubpassInput: |
| sampledType = EbtUInt; |
| break; |
| case EbtUSampler2DArray: |
| case EbtUImage2DArray: |
| sampledType = EbtUInt; |
| isArrayed = true; |
| break; |
| case EbtUSampler2DMS: |
| case EbtUImage2DMS: |
| case EbtUSubpassInputMS: |
| sampledType = EbtUInt; |
| isMultisampled = true; |
| break; |
| case EbtUSampler2DMSArray: |
| case EbtUImage2DMSArray: |
| sampledType = EbtUInt; |
| isArrayed = true; |
| isMultisampled = true; |
| break; |
| |
| // 3D images |
| case EbtSampler3D: |
| case EbtImage3D: |
| *dimOut = spv::Dim3D; |
| break; |
| case EbtISampler3D: |
| case EbtIImage3D: |
| sampledType = EbtInt; |
| *dimOut = spv::Dim3D; |
| break; |
| case EbtUSampler3D: |
| case EbtUImage3D: |
| sampledType = EbtUInt; |
| *dimOut = spv::Dim3D; |
| break; |
| |
| // Float cube images |
| case EbtSamplerCube: |
| case EbtImageCube: |
| *dimOut = spv::DimCube; |
| break; |
| case EbtSamplerCubeArray: |
| case EbtImageCubeArray: |
| *dimOut = spv::DimCube; |
| isArrayed = true; |
| break; |
| case EbtSamplerCubeArrayShadow: |
| *dimOut = spv::DimCube; |
| isDepth = true; |
| isArrayed = true; |
| break; |
| case EbtSamplerCubeShadow: |
| *dimOut = spv::DimCube; |
| isDepth = true; |
| break; |
| |
| // Integer cube images |
| case EbtISamplerCube: |
| case EbtIImageCube: |
| sampledType = EbtInt; |
| *dimOut = spv::DimCube; |
| break; |
| case EbtISamplerCubeArray: |
| case EbtIImageCubeArray: |
| sampledType = EbtInt; |
| *dimOut = spv::DimCube; |
| isArrayed = true; |
| break; |
| |
| // Unsigned integer cube images |
| case EbtUSamplerCube: |
| case EbtUImageCube: |
| sampledType = EbtUInt; |
| *dimOut = spv::DimCube; |
| break; |
| case EbtUSamplerCubeArray: |
| case EbtUImageCubeArray: |
| sampledType = EbtUInt; |
| *dimOut = spv::DimCube; |
| isArrayed = true; |
| break; |
| |
| // Float 1D images |
| case EbtSampler1D: |
| case EbtImage1D: |
| *dimOut = spv::Dim1D; |
| break; |
| case EbtSampler1DArray: |
| case EbtImage1DArray: |
| *dimOut = spv::Dim1D; |
| isArrayed = true; |
| break; |
| case EbtSampler1DShadow: |
| *dimOut = spv::Dim1D; |
| isDepth = true; |
| break; |
| case EbtSampler1DArrayShadow: |
| *dimOut = spv::Dim1D; |
| isDepth = true; |
| isArrayed = true; |
| break; |
| |
| // Integer 1D images |
| case EbtISampler1D: |
| case EbtIImage1D: |
| sampledType = EbtInt; |
| *dimOut = spv::Dim1D; |
| break; |
| case EbtISampler1DArray: |
| case EbtIImage1DArray: |
| sampledType = EbtInt; |
| *dimOut = spv::Dim1D; |
| isArrayed = true; |
| break; |
| |
| // Unsigned integer 1D images |
| case EbtUSampler1D: |
| case EbtUImage1D: |
| sampledType = EbtUInt; |
| *dimOut = spv::Dim1D; |
| break; |
| case EbtUSampler1DArray: |
| case EbtUImage1DArray: |
| sampledType = EbtUInt; |
| *dimOut = spv::Dim1D; |
| isArrayed = true; |
| break; |
| |
| // Rect images |
| case EbtSampler2DRect: |
| case EbtImageRect: |
| *dimOut = spv::DimRect; |
| break; |
| case EbtSampler2DRectShadow: |
| *dimOut = spv::DimRect; |
| isDepth = true; |
| break; |
| case EbtISampler2DRect: |
| case EbtIImageRect: |
| sampledType = EbtInt; |
| *dimOut = spv::DimRect; |
| break; |
| case EbtUSampler2DRect: |
| case EbtUImageRect: |
| sampledType = EbtUInt; |
| *dimOut = spv::DimRect; |
| break; |
| |
| // Image buffers |
| case EbtSamplerBuffer: |
| case EbtImageBuffer: |
| *dimOut = spv::DimBuffer; |
| break; |
| case EbtISamplerBuffer: |
| case EbtIImageBuffer: |
| sampledType = EbtInt; |
| *dimOut = spv::DimBuffer; |
| break; |
| case EbtUSamplerBuffer: |
| case EbtUImageBuffer: |
| sampledType = EbtUInt; |
| *dimOut = spv::DimBuffer; |
| break; |
| default: |
| UNREACHABLE(); |
| } |
| |
| // Get id of the component type of the image |
| SpirvType sampledSpirvType; |
| sampledSpirvType.type = sampledType; |
| |
| *sampledTypeOut = getSpirvTypeData(sampledSpirvType, nullptr).id; |
| |
| const bool isSampledImage = IsSampler(type); |
| |
| // Set flags based on SPIR-V required values. See OpTypeImage: |
| // |
| // - For depth: 0 = non-depth, 1 = depth |
| // - For arrayed: 0 = non-arrayed, 1 = arrayed |
| // - For multisampled: 0 = single-sampled, 1 = multisampled |
| // - For sampled: 1 = sampled, 2 = storage |
| // |
| *depthOut = spirv::LiteralInteger(isDepth ? 1 : 0); |
| *arrayedOut = spirv::LiteralInteger(isArrayed ? 1 : 0); |
| *multisampledOut = spirv::LiteralInteger(isMultisampled ? 1 : 0); |
| *sampledOut = spirv::LiteralInteger(isSampledImage ? 1 : 2); |
| |
| // Add the necessary capability based on parameters. The SPIR-V spec section 3.8 Dim specfies |
| // the required capabilities: |
| // |
| // Dim Sampled Storage Storage Array |
| // -------------------------------------------------------------- |
| // 1D Sampled1D Image1D |
| // 2D Shader ImageMSArray |
| // 3D |
| // Cube Shader ImageCubeArray |
| // Rect SampledRect ImageRect |
| // Buffer SampledBuffer ImageBuffer |
| // |
| // Additionally, the SubpassData Dim requires the InputAttachment capability. |
| // |
| // Note that the Shader capability is always unconditionally added. |
| // |
| switch (*dimOut) |
| { |
| case spv::Dim1D: |
| addCapability(isSampledImage ? spv::CapabilitySampled1D : spv::CapabilityImage1D); |
| break; |
| case spv::Dim2D: |
| if (!isSampledImage && isArrayed && isMultisampled) |
| { |
| addCapability(spv::CapabilityImageMSArray); |
| } |
| break; |
| case spv::Dim3D: |
| break; |
| case spv::DimCube: |
| if (!isSampledImage && isArrayed && isMultisampled) |
| { |
| addCapability(spv::CapabilityImageCubeArray); |
| } |
| break; |
| case spv::DimRect: |
| addCapability(isSampledImage ? spv::CapabilitySampledRect : spv::CapabilityImageRect); |
| break; |
| case spv::DimBuffer: |
| addCapability(isSampledImage ? spv::CapabilitySampledBuffer |
| : spv::CapabilityImageBuffer); |
| break; |
| case spv::DimSubpassData: |
| addCapability(spv::CapabilityInputAttachment); |
| break; |
| default: |
| UNREACHABLE(); |
| } |
| } |
| |
| spv::ImageFormat SPIRVBuilder::getImageFormat(TLayoutImageInternalFormat imageInternalFormat) |
| { |
| switch (imageInternalFormat) |
| { |
| case EiifUnspecified: |
| return spv::ImageFormatUnknown; |
| case EiifRGBA32F: |
| return spv::ImageFormatRgba32f; |
| case EiifRGBA16F: |
| return spv::ImageFormatRgba16f; |
| case EiifR32F: |
| return spv::ImageFormatR32f; |
| case EiifRGBA32UI: |
| return spv::ImageFormatRgba32ui; |
| case EiifRGBA16UI: |
| return spv::ImageFormatRgba16ui; |
| case EiifRGBA8UI: |
| return spv::ImageFormatRgba8ui; |
| case EiifR32UI: |
| return spv::ImageFormatR32ui; |
| case EiifRGBA32I: |
| return spv::ImageFormatRgba32i; |
| case EiifRGBA16I: |
| return spv::ImageFormatRgba16i; |
| case EiifRGBA8I: |
| return spv::ImageFormatRgba8i; |
| case EiifR32I: |
| return spv::ImageFormatR32i; |
| case EiifRGBA8: |
| return spv::ImageFormatRgba8; |
| case EiifRGBA8_SNORM: |
| return spv::ImageFormatRgba8Snorm; |
| default: |
| UNREACHABLE(); |
| return spv::ImageFormatUnknown; |
| } |
| } |
| |
| spirv::IdRef SPIRVBuilder::getBoolConstant(bool value) |
| { |
| uint32_t asInt = static_cast<uint32_t>(value); |
| |
| spirv::IdRef constantId = mBoolConstants[asInt]; |
| |
| if (!constantId.valid()) |
| { |
| SpirvType boolType; |
| boolType.type = EbtBool; |
| |
| const spirv::IdRef boolTypeId = getSpirvTypeData(boolType, nullptr).id; |
| |
| mBoolConstants[asInt] = constantId = getNewId({}); |
| if (value) |
| { |
| spirv::WriteConstantTrue(&mSpirvTypeAndConstantDecls, boolTypeId, constantId); |
| } |
| else |
| { |
| spirv::WriteConstantFalse(&mSpirvTypeAndConstantDecls, boolTypeId, constantId); |
| } |
| } |
| |
| return constantId; |
| } |
| |
| spirv::IdRef SPIRVBuilder::getBasicConstantHelper(uint32_t value, |
| TBasicType type, |
| angle::HashMap<uint32_t, spirv::IdRef> *constants) |
| { |
| auto iter = constants->find(value); |
| if (iter != constants->end()) |
| { |
| return iter->second; |
| } |
| |
| SpirvType spirvType; |
| spirvType.type = type; |
| |
| const spirv::IdRef typeId = getSpirvTypeData(spirvType, nullptr).id; |
| const spirv::IdRef constantId = getNewId({}); |
| |
| spirv::WriteConstant(&mSpirvTypeAndConstantDecls, typeId, constantId, |
| spirv::LiteralContextDependentNumber(value)); |
| |
| return constants->insert({value, constantId}).first->second; |
| } |
| |
| spirv::IdRef SPIRVBuilder::getUintConstant(uint32_t value) |
| { |
| return getBasicConstantHelper(value, EbtUInt, &mUintConstants); |
| } |
| |
| spirv::IdRef SPIRVBuilder::getIntConstant(int32_t value) |
| { |
| uint32_t asUint = static_cast<uint32_t>(value); |
| return getBasicConstantHelper(asUint, EbtInt, &mIntConstants); |
| } |
| |
| spirv::IdRef SPIRVBuilder::getFloatConstant(float value) |
| { |
| union |
| { |
| float f; |
| uint32_t u; |
| } asUint; |
| asUint.f = value; |
| return getBasicConstantHelper(asUint.u, EbtFloat, &mFloatConstants); |
| } |
| |
| spirv::IdRef SPIRVBuilder::getNullConstant(spirv::IdRef typeId) |
| { |
| if (typeId >= mNullConstants.size()) |
| { |
| mNullConstants.resize(typeId + 1); |
| } |
| |
| if (!mNullConstants[typeId].valid()) |
| { |
| const spirv::IdRef constantId = getNewId({}); |
| mNullConstants[typeId] = constantId; |
| |
| spirv::WriteConstantNull(&mSpirvTypeAndConstantDecls, typeId, constantId); |
| } |
| |
| return mNullConstants[typeId]; |
| } |
| |
| spirv::IdRef SPIRVBuilder::getNullVectorConstantHelper(TBasicType type, int size) |
| { |
| SpirvType vecType; |
| vecType.type = type; |
| vecType.primarySize = static_cast<uint8_t>(size); |
| |
| return getNullConstant(getSpirvTypeData(vecType, nullptr).id); |
| } |
| |
| spirv::IdRef SPIRVBuilder::getVectorConstantHelper(spirv::IdRef valueId, TBasicType type, int size) |
| { |
| if (size == 1) |
| { |
| return valueId; |
| } |
| |
| SpirvType vecType; |
| vecType.type = type; |
| vecType.primarySize = static_cast<uint8_t>(size); |
| |
| const spirv::IdRef typeId = getSpirvTypeData(vecType, nullptr).id; |
| const spirv::IdRefList valueIds(size, valueId); |
| |
| return getCompositeConstant(typeId, valueIds); |
| } |
| |
| spirv::IdRef SPIRVBuilder::getUvecConstant(uint32_t value, int size) |
| { |
| if (value == 0) |
| { |
| return getNullVectorConstantHelper(EbtUInt, size); |
| } |
| |
| const spirv::IdRef valueId = getUintConstant(value); |
| return getVectorConstantHelper(valueId, EbtUInt, size); |
| } |
| |
| spirv::IdRef SPIRVBuilder::getIvecConstant(int32_t value, int size) |
| { |
| if (value == 0) |
| { |
| return getNullVectorConstantHelper(EbtInt, size); |
| } |
| |
| const spirv::IdRef valueId = getIntConstant(value); |
| return getVectorConstantHelper(valueId, EbtInt, size); |
| } |
| |
| spirv::IdRef SPIRVBuilder::getVecConstant(float value, int size) |
| { |
| if (value == 0) |
| { |
| return getNullVectorConstantHelper(EbtFloat, size); |
| } |
| |
| const spirv::IdRef valueId = getFloatConstant(value); |
| return getVectorConstantHelper(valueId, EbtFloat, size); |
| } |
| |
| spirv::IdRef SPIRVBuilder::getCompositeConstant(spirv::IdRef typeId, const spirv::IdRefList &values) |
| { |
| SpirvIdAndIdList key{typeId, values}; |
| |
| auto iter = mCompositeConstants.find(key); |
| if (iter == mCompositeConstants.end()) |
| { |
| const spirv::IdRef constantId = getNewId({}); |
| |
| spirv::WriteConstantComposite(&mSpirvTypeAndConstantDecls, typeId, constantId, values); |
| |
| iter = mCompositeConstants.insert({key, constantId}).first; |
| } |
| |
| return iter->second; |
| } |
| |
| void SPIRVBuilder::startNewFunction(spirv::IdRef functionId, const TFunction *func) |
| { |
| ASSERT(mSpirvCurrentFunctionBlocks.empty()); |
| |
| // Add the first block of the function. |
| mSpirvCurrentFunctionBlocks.emplace_back(); |
| mSpirvCurrentFunctionBlocks.back().labelId = getNewId({}); |
| |
| // Output debug information. |
| spirv::WriteName(&mSpirvDebug, functionId, hashFunctionName(func).data()); |
| } |
| |
| void SPIRVBuilder::assembleSpirvFunctionBlocks() |
| { |
| // Take all the blocks and place them in the functions section of SPIR-V in sequence. |
| for (const SpirvBlock &block : mSpirvCurrentFunctionBlocks) |
| { |
| // Every block must be properly terminated. |
| ASSERT(block.isTerminated); |
| |
| // Generate the OpLabel instruction for the block. |
| spirv::WriteLabel(&mSpirvFunctions, block.labelId); |
| |
| // Add the variable declarations if any. |
| mSpirvFunctions.insert(mSpirvFunctions.end(), block.localVariables.begin(), |
| block.localVariables.end()); |
| |
| // Add the body of the block. |
| mSpirvFunctions.insert(mSpirvFunctions.end(), block.body.begin(), block.body.end()); |
| } |
| |
| // Clean up. |
| mSpirvCurrentFunctionBlocks.clear(); |
| } |
| |
| spirv::IdRef SPIRVBuilder::declareVariable(spirv::IdRef typeId, |
| spv::StorageClass storageClass, |
| const SpirvDecorations &decorations, |
| spirv::IdRef *initializerId, |
| const char *name) |
| { |
| const bool isFunctionLocal = storageClass == spv::StorageClassFunction; |
| |
| // Make sure storage class is consistent with where the variable is declared. |
| ASSERT(!isFunctionLocal || !mSpirvCurrentFunctionBlocks.empty()); |
| |
| // Function-local variables go in the first block of the function, while the rest are in the |
| // global variables section. |
| spirv::Blob *spirvSection = isFunctionLocal |
| ? &mSpirvCurrentFunctionBlocks.front().localVariables |
| : &mSpirvVariableDecls; |
| |
| const spirv::IdRef variableId = getNewId(decorations); |
| const spirv::IdRef typePointerId = getTypePointerId(typeId, storageClass); |
| |
| spirv::WriteVariable(spirvSection, typePointerId, variableId, storageClass, initializerId); |
| |
| // Output debug information. |
| if (name) |
| { |
| spirv::WriteName(&mSpirvDebug, variableId, name); |
| } |
| |
| return variableId; |
| } |
| |
| spirv::IdRef SPIRVBuilder::declareSpecConst(TBasicType type, int id, const char *name) |
| { |
| SpirvType spirvType; |
| spirvType.type = type; |
| |
| const spirv::IdRef typeId = getSpirvTypeData(spirvType, nullptr).id; |
| const spirv::IdRef specConstId = getNewId({}); |
| |
| // Note: all spec constants are 0 initialized by the translator. |
| if (type == EbtBool) |
| { |
| spirv::WriteSpecConstantFalse(&mSpirvTypeAndConstantDecls, typeId, specConstId); |
| } |
| else |
| { |
| spirv::WriteSpecConstant(&mSpirvTypeAndConstantDecls, typeId, specConstId, |
| spirv::LiteralContextDependentNumber(0)); |
| } |
| |
| // Add the SpecId decoration |
| spirv::WriteDecorate(&mSpirvDecorations, specConstId, spv::DecorationSpecId, |
| {spirv::LiteralInteger(id)}); |
| |
| // Output debug information. |
| if (name) |
| { |
| spirv::WriteName(&mSpirvDebug, specConstId, name); |
| } |
| |
| return specConstId; |
| } |
| |
| void SPIRVBuilder::startConditional(size_t blockCount, bool isContinuable, bool isBreakable) |
| { |
| mConditionalStack.emplace_back(); |
| SpirvConditional &conditional = mConditionalStack.back(); |
| |
| // Create the requested number of block ids. |
| conditional.blockIds.resize(blockCount); |
| for (spirv::IdRef &blockId : conditional.blockIds) |
| { |
| blockId = getNewId({}); |
| } |
| |
| conditional.isContinuable = isContinuable; |
| conditional.isBreakable = isBreakable; |
| |
| // Don't automatically start the next block. The caller needs to generate instructions based on |
| // the ids that were just generated above. |
| } |
| |
| void SPIRVBuilder::nextConditionalBlock() |
| { |
| ASSERT(!mConditionalStack.empty()); |
| SpirvConditional &conditional = mConditionalStack.back(); |
| |
| ASSERT(conditional.nextBlockToWrite < conditional.blockIds.size()); |
| const spirv::IdRef blockId = conditional.blockIds[conditional.nextBlockToWrite++]; |
| |
| // The previous block must have properly terminated. |
| ASSERT(isCurrentFunctionBlockTerminated()); |
| |
| // Generate a new block. |
| mSpirvCurrentFunctionBlocks.emplace_back(); |
| mSpirvCurrentFunctionBlocks.back().labelId = blockId; |
| } |
| |
| void SPIRVBuilder::endConditional() |
| { |
| ASSERT(!mConditionalStack.empty()); |
| |
| // No blocks should be left. |
| ASSERT(mConditionalStack.back().nextBlockToWrite == mConditionalStack.back().blockIds.size()); |
| |
| mConditionalStack.pop_back(); |
| } |
| |
| bool SPIRVBuilder::isInLoop() const |
| { |
| for (const SpirvConditional &conditional : mConditionalStack) |
| { |
| if (conditional.isContinuable) |
| { |
| return true; |
| } |
| } |
| |
| return false; |
| } |
| |
| spirv::IdRef SPIRVBuilder::getBreakTargetId() const |
| { |
| for (size_t index = mConditionalStack.size(); index > 0; --index) |
| { |
| const SpirvConditional &conditional = mConditionalStack[index - 1]; |
| |
| if (conditional.isBreakable) |
| { |
| // The target of break; is always the merge block, and the merge block is always the |
| // last block. |
| return conditional.blockIds.back(); |
| } |
| } |
| |
| UNREACHABLE(); |
| return spirv::IdRef{}; |
| } |
| |
| spirv::IdRef SPIRVBuilder::getContinueTargetId() const |
| { |
| for (size_t index = mConditionalStack.size(); index > 0; --index) |
| { |
| const SpirvConditional &conditional = mConditionalStack[index - 1]; |
| |
| if (conditional.isContinuable) |
| { |
| // The target of continue; is always the block before merge, so it's the one before |
| // last. |
| ASSERT(conditional.blockIds.size() > 2); |
| return conditional.blockIds[conditional.blockIds.size() - 2]; |
| } |
| } |
| |
| UNREACHABLE(); |
| return spirv::IdRef{}; |
| } |
| |
| uint32_t SPIRVBuilder::nextUnusedBinding() |
| { |
| return mNextUnusedBinding++; |
| } |
| |
| uint32_t SPIRVBuilder::nextUnusedInputLocation(uint32_t consumedCount) |
| { |
| uint32_t nextUnused = mNextUnusedInputLocation; |
| mNextUnusedInputLocation += consumedCount; |
| return nextUnused; |
| } |
| |
| uint32_t SPIRVBuilder::nextUnusedOutputLocation(uint32_t consumedCount) |
| { |
| uint32_t nextUnused = mNextUnusedOutputLocation; |
| mNextUnusedOutputLocation += consumedCount; |
| return nextUnused; |
| } |
| |
| bool SPIRVBuilder::isInvariantOutput(const TType &type) const |
| { |
| return IsInvariant(type, mCompiler); |
| } |
| |
| void SPIRVBuilder::addCapability(spv::Capability capability) |
| { |
| mCapabilities.insert(capability); |
| } |
| |
| void SPIRVBuilder::setEntryPointId(spirv::IdRef id) |
| { |
| ASSERT(!mEntryPointId.valid()); |
| mEntryPointId = id; |
| } |
| |
| void SPIRVBuilder::addEntryPointInterfaceVariableId(spirv::IdRef id) |
| { |
| mEntryPointInterfaceList.push_back(id); |
| } |
| |
| void SPIRVBuilder::writePerVertexBuiltIns(const TType &type, spirv::IdRef typeId) |
| { |
| ASSERT(type.isInterfaceBlock()); |
| const TInterfaceBlock *block = type.getInterfaceBlock(); |
| |
| uint32_t fieldIndex = 0; |
| for (const TField *field : block->fields()) |
| { |
| spv::BuiltIn decorationValue = spv::BuiltInPosition; |
| switch (field->type()->getQualifier()) |
| { |
| case EvqPosition: |
| decorationValue = spv::BuiltInPosition; |
| break; |
| case EvqPointSize: |
| decorationValue = spv::BuiltInPointSize; |
| break; |
| case EvqClipDistance: |
| decorationValue = spv::BuiltInClipDistance; |
| break; |
| case EvqCullDistance: |
| decorationValue = spv::BuiltInCullDistance; |
| break; |
| default: |
| UNREACHABLE(); |
| } |
| |
| spirv::WriteMemberDecorate(&mSpirvDecorations, typeId, spirv::LiteralInteger(fieldIndex++), |
| spv::DecorationBuiltIn, |
| {spirv::LiteralInteger(decorationValue)}); |
| } |
| } |
| |
| void SPIRVBuilder::writeInterfaceVariableDecorations(const TType &type, spirv::IdRef variableId) |
| { |
| const TLayoutQualifier &layoutQualifier = type.getLayoutQualifier(); |
| |
| const bool isVarying = IsVarying(type.getQualifier()); |
| const bool needsSetBinding = |
| IsSampler(type.getBasicType()) || |
| (type.isInterfaceBlock() && |
| (type.getQualifier() == EvqUniform || type.getQualifier() == EvqBuffer)) || |
| IsImage(type.getBasicType()) || IsSubpassInputType(type.getBasicType()); |
| const bool needsLocation = type.getQualifier() == EvqAttribute || |
| type.getQualifier() == EvqVertexIn || |
| type.getQualifier() == EvqFragmentOut || isVarying; |
| const bool needsInputAttachmentIndex = IsSubpassInputType(type.getBasicType()); |
| const bool needsBlendIndex = |
| type.getQualifier() == EvqFragmentOut && layoutQualifier.index >= 0; |
| |
| // If the resource declaration requires set & binding, add the DescriptorSet and Binding |
| // decorations. |
| if (needsSetBinding) |
| { |
| spirv::WriteDecorate(&mSpirvDecorations, variableId, spv::DecorationDescriptorSet, |
| {spirv::LiteralInteger(0)}); |
| spirv::WriteDecorate(&mSpirvDecorations, variableId, spv::DecorationBinding, |
| {spirv::LiteralInteger(nextUnusedBinding())}); |
| } |
| |
| if (needsLocation) |
| { |
| const unsigned int locationCount = |
| CalculateVaryingLocationCount(type, gl::ToGLenum(mShaderType)); |
| const uint32_t location = IsShaderIn(type.getQualifier()) |
| ? nextUnusedInputLocation(locationCount) |
| : nextUnusedOutputLocation(locationCount); |
| |
| spirv::WriteDecorate(&mSpirvDecorations, variableId, spv::DecorationLocation, |
| {spirv::LiteralInteger(location)}); |
| } |
| |
| // If the resource declaration is an input attachment, add the InputAttachmentIndex decoration. |
| if (needsInputAttachmentIndex) |
| { |
| spirv::WriteDecorate(&mSpirvDecorations, variableId, spv::DecorationInputAttachmentIndex, |
| {spirv::LiteralInteger(layoutQualifier.inputAttachmentIndex)}); |
| } |
| |
| if (needsBlendIndex) |
| { |
| spirv::WriteDecorate(&mSpirvDecorations, variableId, spv::DecorationIndex, |
| {spirv::LiteralInteger(layoutQualifier.index)}); |
| } |
| |
| // Handle interpolation and auxiliary decorations on varyings |
| if (isVarying) |
| { |
| writeInterpolationDecoration(type.getQualifier(), variableId, |
| std::numeric_limits<uint32_t>::max()); |
| } |
| } |
| |
| void SPIRVBuilder::writeBranchConditional(spirv::IdRef conditionValue, |
| spirv::IdRef trueBlock, |
| spirv::IdRef falseBlock, |
| spirv::IdRef mergeBlock) |
| { |
| // Generate the following: |
| // |
| // OpSelectionMerge %mergeBlock None |
| // OpBranchConditional %conditionValue %trueBlock %falseBlock |
| // |
| spirv::WriteSelectionMerge(getSpirvCurrentFunctionBlock(), mergeBlock, |
| spv::SelectionControlMaskNone); |
| spirv::WriteBranchConditional(getSpirvCurrentFunctionBlock(), conditionValue, trueBlock, |
| falseBlock, {}); |
| terminateCurrentFunctionBlock(); |
| |
| // Start the true or false block, whichever exists. |
| nextConditionalBlock(); |
| } |
| |
| void SPIRVBuilder::writeBranchConditionalBlockEnd() |
| { |
| if (!isCurrentFunctionBlockTerminated()) |
| { |
| // Insert a branch to the merge block at the end of each if-else block, unless the block is |
| // already terminated, such as with a return or discard. |
| const spirv::IdRef mergeBlock = getCurrentConditional()->blockIds.back(); |
| |
| spirv::WriteBranch(getSpirvCurrentFunctionBlock(), mergeBlock); |
| terminateCurrentFunctionBlock(); |
| } |
| |
| // Move on to the next block. |
| nextConditionalBlock(); |
| } |
| |
| void SPIRVBuilder::writeLoopHeader(spirv::IdRef branchToBlock, |
| spirv::IdRef continueBlock, |
| spirv::IdRef mergeBlock) |
| { |
| // First, jump to the header block: |
| // |
| // OpBranch %header |
| // |
| const spirv::IdRef headerBlock = mConditionalStack.back().blockIds[0]; |
| spirv::WriteBranch(getSpirvCurrentFunctionBlock(), headerBlock); |
| terminateCurrentFunctionBlock(); |
| |
| // Start the header block. |
| nextConditionalBlock(); |
| |
| // Generate the following: |
| // |
| // OpLoopMerge %mergeBlock %continueBlock None |
| // OpBranch %branchToBlock (%cond or if do-while, %body) |
| // |
| spirv::WriteLoopMerge(getSpirvCurrentFunctionBlock(), mergeBlock, continueBlock, |
| spv::LoopControlMaskNone); |
| spirv::WriteBranch(getSpirvCurrentFunctionBlock(), branchToBlock); |
| terminateCurrentFunctionBlock(); |
| |
| // Start the next block, which is either %cond or %body. |
| nextConditionalBlock(); |
| } |
| |
| void SPIRVBuilder::writeLoopConditionEnd(spirv::IdRef conditionValue, |
| spirv::IdRef branchToBlock, |
| spirv::IdRef mergeBlock) |
| { |
| // Generate the following: |
| // |
| // OpBranchConditional %conditionValue %branchToBlock %mergeBlock |
| // |
| // %branchToBlock is either %body or if do-while, %header |
| // |
| spirv::WriteBranchConditional(getSpirvCurrentFunctionBlock(), conditionValue, branchToBlock, |
| mergeBlock, {}); |
| terminateCurrentFunctionBlock(); |
| |
| // Start the next block, which is either %continue or %body. |
| nextConditionalBlock(); |
| } |
| |
| void SPIRVBuilder::writeLoopContinueEnd(spirv::IdRef headerBlock) |
| { |
| // Generate the following: |
| // |
| // OpBranch %headerBlock |
| // |
| spirv::WriteBranch(getSpirvCurrentFunctionBlock(), headerBlock); |
| terminateCurrentFunctionBlock(); |
| |
| // Start the next block, which is %body. |
| nextConditionalBlock(); |
| } |
| |
| void SPIRVBuilder::writeLoopBodyEnd(spirv::IdRef continueBlock) |
| { |
| // Generate the following: |
| // |
| // OpBranch %continueBlock |
| // |
| // This is only done if the block isn't already terminated in another way, such as with an |
| // unconditional continue/etc at the end of the loop. |
| if (!isCurrentFunctionBlockTerminated()) |
| { |
| spirv::WriteBranch(getSpirvCurrentFunctionBlock(), continueBlock); |
| terminateCurrentFunctionBlock(); |
| } |
| |
| // Start the next block, which is %merge or if while, %continue. |
| nextConditionalBlock(); |
| } |
| |
| void SPIRVBuilder::writeSwitch(spirv::IdRef conditionValue, |
| spirv::IdRef defaultBlock, |
| const spirv::PairLiteralIntegerIdRefList &targetPairList, |
| spirv::IdRef mergeBlock) |
| { |
| // Generate the following: |
| // |
| // OpSelectionMerge %mergeBlock None |
| // OpSwitch %conditionValue %defaultBlock A %ABlock B %BBlock ... |
| // |
| spirv::WriteSelectionMerge(getSpirvCurrentFunctionBlock(), mergeBlock, |
| spv::SelectionControlMaskNone); |
| spirv::WriteSwitch(getSpirvCurrentFunctionBlock(), conditionValue, defaultBlock, |
| targetPairList); |
| terminateCurrentFunctionBlock(); |
| |
| // Start the next case block. |
| nextConditionalBlock(); |
| } |
| |
| void SPIRVBuilder::writeSwitchCaseBlockEnd() |
| { |
| if (!isCurrentFunctionBlockTerminated()) |
| { |
| // If a case does not end in branch, insert a branch to the next block, implementing |
| // fallthrough. For the last block, the branch target would automatically be the merge |
| // block. |
| const SpirvConditional *conditional = getCurrentConditional(); |
| const spirv::IdRef nextBlock = conditional->blockIds[conditional->nextBlockToWrite]; |
| |
| spirv::WriteBranch(getSpirvCurrentFunctionBlock(), nextBlock); |
| terminateCurrentFunctionBlock(); |
| } |
| |
| // Move on to the next block. |
| nextConditionalBlock(); |
| } |
| |
| void SPIRVBuilder::writeMemberDecorations(const SpirvType &type, spirv::IdRef typeId) |
| { |
| ASSERT(type.block != nullptr); |
| |
| uint32_t fieldIndex = 0; |
| |
| for (const TField *field : type.block->fields()) |
| { |
| const TType &fieldType = *field->type(); |
| |
| // Add invariant decoration if any. |
| if (type.typeSpec.isInvariantBlock || fieldType.isInvariant()) |
| { |
| spirv::WriteMemberDecorate(&mSpirvDecorations, typeId, |
| spirv::LiteralInteger(fieldIndex), spv::DecorationInvariant, |
| {}); |
| } |
| |
| // Add matrix decorations if any. |
| if (fieldType.isMatrix()) |
| { |
| // ColMajor or RowMajor |
| const bool isRowMajor = |
| IsBlockFieldRowMajorQualified(fieldType, type.typeSpec.isRowMajorQualifiedBlock); |
| spirv::WriteMemberDecorate( |
| &mSpirvDecorations, typeId, spirv::LiteralInteger(fieldIndex), |
| isRowMajor ? spv::DecorationRowMajor : spv::DecorationColMajor, {}); |
| } |
| |
| // Add interpolation and auxiliary decorations |
| writeInterpolationDecoration(fieldType.getQualifier(), typeId, fieldIndex); |
| |
| // Add other decorations. |
| SpirvDecorations decorations = getDecorations(fieldType); |
| for (const spv::Decoration decoration : decorations) |
| { |
| spirv::WriteMemberDecorate(&mSpirvDecorations, typeId, |
| spirv::LiteralInteger(fieldIndex), decoration, {}); |
| } |
| |
| ++fieldIndex; |
| } |
| } |
| |
| void SPIRVBuilder::writeInterpolationDecoration(TQualifier qualifier, |
| spirv::IdRef id, |
| uint32_t fieldIndex) |
| { |
| spv::Decoration decoration = spv::DecorationMax; |
| |
| switch (qualifier) |
| { |
| case EvqSmooth: |
| case EvqSmoothOut: |
| case EvqSmoothIn: |
| // No decoration in SPIR-V for smooth, this is the default interpolation. |
| return; |
| |
| case EvqFlat: |
| case EvqFlatOut: |
| case EvqFlatIn: |
| decoration = spv::DecorationFlat; |
| break; |
| |
| case EvqNoPerspective: |
| case EvqNoPerspectiveOut: |
| case EvqNoPerspectiveIn: |
| decoration = spv::DecorationNoPerspective; |
| break; |
| |
| case EvqCentroid: |
| case EvqCentroidOut: |
| case EvqCentroidIn: |
| decoration = spv::DecorationCentroid; |
| break; |
| |
| case EvqSample: |
| case EvqSampleOut: |
| case EvqSampleIn: |
| decoration = spv::DecorationSample; |
| addCapability(spv::CapabilitySampleRateShading); |
| break; |
| |
| default: |
| return; |
| } |
| |
| if (fieldIndex != std::numeric_limits<uint32_t>::max()) |
| { |
| spirv::WriteMemberDecorate(&mSpirvDecorations, id, spirv::LiteralInteger(fieldIndex), |
| decoration, {}); |
| } |
| else |
| { |
| spirv::WriteDecorate(&mSpirvDecorations, id, decoration, {}); |
| } |
| } |
| |
| ImmutableString SPIRVBuilder::hashName(const TSymbol *symbol) |
| { |
| return HashName(symbol, mHashFunction, &mNameMap); |
| } |
| |
| ImmutableString SPIRVBuilder::hashTypeName(const TType &type) |
| { |
| return GetTypeName(type, mHashFunction, &mNameMap); |
| } |
| |
| ImmutableString SPIRVBuilder::hashFieldName(const TField *field) |
| { |
| ASSERT(field->symbolType() != SymbolType::Empty); |
| if (field->symbolType() == SymbolType::UserDefined) |
| { |
| return HashName(field->name(), mHashFunction, &mNameMap); |
| } |
| |
| return field->name(); |
| } |
| |
| ImmutableString SPIRVBuilder::hashFunctionName(const TFunction *func) |
| { |
| if (func->isMain()) |
| { |
| return func->name(); |
| } |
| |
| return hashName(func); |
| } |
| |
| spirv::Blob SPIRVBuilder::getSpirv() |
| { |
| ASSERT(mConditionalStack.empty()); |
| |
| spirv::Blob result; |
| |
| // Reserve a minimum amount of memory. |
| // |
| // 5 for header + |
| // a number of capabilities + |
| // size of already generated instructions. |
| // |
| // The actual size is larger due to other metadata instructions such as extensions, |
| // OpExtInstImport, OpEntryPoint, OpExecutionMode etc. |
| result.reserve(5 + mCapabilities.size() * 2 + mSpirvDebug.size() + mSpirvDecorations.size() + |
| mSpirvTypeAndConstantDecls.size() + mSpirvTypePointerDecls.size() + |
| mSpirvFunctionTypeDecls.size() + mSpirvVariableDecls.size() + |
| mSpirvFunctions.size()); |
| |
| // Generate the SPIR-V header. |
| spirv::WriteSpirvHeader(&result, mNextAvailableId); |
| |
| // Generate metadata in the following order: |
| // |
| // - OpCapability instructions. |
| for (spv::Capability capability : mCapabilities) |
| { |
| spirv::WriteCapability(&result, capability); |
| } |
| |
| // - OpExtension instructions (TODO: http://anglebug.com/4889) |
| |
| // - OpExtInstImport |
| if (mExtInstImportIdStd.valid()) |
| { |
| spirv::WriteExtInstImport(&result, mExtInstImportIdStd, "GLSL.std.450"); |
| } |
| |
| // - OpMemoryModel |
| spirv::WriteMemoryModel(&result, spv::AddressingModelLogical, spv::MemoryModelGLSL450); |
| |
| // - OpEntryPoint |
| constexpr gl::ShaderMap<spv::ExecutionModel> kExecutionModels = { |
| {gl::ShaderType::Vertex, spv::ExecutionModelVertex}, |
| {gl::ShaderType::TessControl, spv::ExecutionModelTessellationControl}, |
| {gl::ShaderType::TessEvaluation, spv::ExecutionModelTessellationEvaluation}, |
| {gl::ShaderType::Geometry, spv::ExecutionModelGeometry}, |
| {gl::ShaderType::Fragment, spv::ExecutionModelFragment}, |
| {gl::ShaderType::Compute, spv::ExecutionModelGLCompute}, |
| }; |
| spirv::WriteEntryPoint(&result, kExecutionModels[mShaderType], mEntryPointId, "main", |
| mEntryPointInterfaceList); |
| |
| // - OpExecutionMode instructions |
| generateExecutionModes(&result); |
| |
| // - OpSource instruction. |
| // |
| // This is to support debuggers and capture/replay tools and isn't strictly necessary. |
| spirv::WriteSource(&result, spv::SourceLanguageGLSL, spirv::LiteralInteger(450), nullptr, |
| nullptr); |
| |
| // Append the already generated sections in order |
| result.insert(result.end(), mSpirvDebug.begin(), mSpirvDebug.end()); |
| result.insert(result.end(), mSpirvDecorations.begin(), mSpirvDecorations.end()); |
| result.insert(result.end(), mSpirvTypeAndConstantDecls.begin(), |
| mSpirvTypeAndConstantDecls.end()); |
| result.insert(result.end(), mSpirvTypePointerDecls.begin(), mSpirvTypePointerDecls.end()); |
| result.insert(result.end(), mSpirvFunctionTypeDecls.begin(), mSpirvFunctionTypeDecls.end()); |
| result.insert(result.end(), mSpirvVariableDecls.begin(), mSpirvVariableDecls.end()); |
| result.insert(result.end(), mSpirvFunctions.begin(), mSpirvFunctions.end()); |
| |
| result.shrink_to_fit(); |
| return result; |
| } |
| |
| void SPIRVBuilder::generateExecutionModes(spirv::Blob *blob) |
| { |
| switch (mShaderType) |
| { |
| case gl::ShaderType::Fragment: |
| spirv::WriteExecutionMode(blob, mEntryPointId, spv::ExecutionModeOriginUpperLeft, {}); |
| |
| if (mCompiler->isEarlyFragmentTestsSpecified() || |
| mCompiler->isEarlyFragmentTestsOptimized()) |
| { |
| spirv::WriteExecutionMode(blob, mEntryPointId, spv::ExecutionModeEarlyFragmentTests, |
| {}); |
| } |
| |
| break; |
| |
| case gl::ShaderType::TessControl: |
| spirv::WriteExecutionMode( |
| blob, mEntryPointId, spv::ExecutionModeOutputVertices, |
| {spirv::LiteralInteger(mCompiler->getTessControlShaderOutputVertices())}); |
| break; |
| |
| case gl::ShaderType::TessEvaluation: |
| { |
| const spv::ExecutionMode inputExecutionMode = GetTessEvalInputExecutionMode( |
| mCompiler->getTessEvaluationShaderInputPrimitiveType()); |
| const spv::ExecutionMode spacingExecutionMode = GetTessEvalSpacingExecutionMode( |
| mCompiler->getTessEvaluationShaderInputVertexSpacingType()); |
| const spv::ExecutionMode orderingExecutionMode = GetTessEvalOrderingExecutionMode( |
| mCompiler->getTessEvaluationShaderInputOrderingType()); |
| |
| spirv::WriteExecutionMode(blob, mEntryPointId, inputExecutionMode, {}); |
| spirv::WriteExecutionMode(blob, mEntryPointId, spacingExecutionMode, {}); |
| spirv::WriteExecutionMode(blob, mEntryPointId, orderingExecutionMode, {}); |
| if (mCompiler->getTessEvaluationShaderInputPointType() == EtetPointMode) |
| { |
| spirv::WriteExecutionMode(blob, mEntryPointId, spv::ExecutionModePointMode, {}); |
| } |
| break; |
| } |
| |
| case gl::ShaderType::Geometry: |
| { |
| const spv::ExecutionMode inputExecutionMode = |
| GetGeometryInputExecutionMode(mCompiler->getGeometryShaderInputPrimitiveType()); |
| const spv::ExecutionMode outputExecutionMode = |
| GetGeometryOutputExecutionMode(mCompiler->getGeometryShaderOutputPrimitiveType()); |
| |
| spirv::WriteExecutionMode(blob, mEntryPointId, inputExecutionMode, {}); |
| spirv::WriteExecutionMode(blob, mEntryPointId, outputExecutionMode, {}); |
| spirv::WriteExecutionMode( |
| blob, mEntryPointId, spv::ExecutionModeOutputVertices, |
| {spirv::LiteralInteger(mCompiler->getGeometryShaderMaxVertices())}); |
| spirv::WriteExecutionMode( |
| blob, mEntryPointId, spv::ExecutionModeInvocations, |
| {spirv::LiteralInteger(mCompiler->getGeometryShaderInvocations())}); |
| |
| break; |
| } |
| |
| case gl::ShaderType::Compute: |
| { |
| const sh::WorkGroupSize &localSize = mCompiler->getComputeShaderLocalSize(); |
| spirv::WriteExecutionMode( |
| blob, mEntryPointId, spv::ExecutionModeLocalSize, |
| {spirv::LiteralInteger(localSize[0]), spirv::LiteralInteger(localSize[1]), |
| spirv::LiteralInteger(localSize[2])}); |
| break; |
| } |
| default: |
| // TODO: other shader types. http://anglebug.com/4889 |
| break; |
| } |
| } |
| |
| } // namespace sh |