| // |
| // 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/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.blockStorage == b.blockStorage && a.isInvariant == b.isInvariant; |
| } |
| |
| // 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.arraySizes.empty() || a.blockStorage == b.blockStorage); |
| } |
| |
| uint32_t GetTotalArrayElements(const SpirvType &type) |
| { |
| uint32_t arraySizeProduct = 1; |
| for (uint32_t arraySize : type.arraySizes) |
| { |
| // For runtime arrays, arraySize will be 0 and should be excluded. |
| arraySizeProduct *= arraySize > 0 ? arraySize : 1; |
| } |
| |
| return arraySizeProduct; |
| } |
| |
| uint32_t GetOutermostArraySize(const SpirvType &type) |
| { |
| uint32_t size = type.arraySizes.back(); |
| return size ? size : 1; |
| } |
| |
| 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; |
| } |
| |
| TLayoutBlockStorage SPIRVBuilder::getBlockStorage(const TType &type) const |
| { |
| // Default to std140 for uniform and std430 for buffer blocks. |
| TLayoutBlockStorage blockStorage = type.getLayoutQualifier().blockStorage; |
| if (IsShaderIoBlock(type.getQualifier()) || blockStorage == EbsStd140 || |
| blockStorage == EbsStd430) |
| { |
| return blockStorage; |
| } |
| |
| if (type.getQualifier() == EvqBuffer) |
| { |
| return EbsStd430; |
| } |
| |
| return EbsStd140; |
| } |
| |
| SpirvType SPIRVBuilder::getSpirvType(const TType &type, TLayoutBlockStorage blockStorage) 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; |
| spirvType.blockStorage = blockStorage; |
| |
| if (type.getStruct() != nullptr) |
| { |
| spirvType.block = type.getStruct(); |
| spirvType.isInvariant = isInvariantOutput(type); |
| } |
| else if (type.isInterfaceBlock()) |
| { |
| spirvType.block = type.getInterfaceBlock(); |
| |
| // Calculate the block storage from the interface block automatically. The fields inherit |
| // from this. |
| if (spirvType.blockStorage == EbsUnspecified) |
| { |
| spirvType.blockStorage = getBlockStorage(type); |
| } |
| } |
| else if (spirvType.arraySizes.empty()) |
| { |
| // No difference in type for non-block non-array types in std140 and std430 block storage. |
| spirvType.blockStorage = EbsUnspecified; |
| } |
| |
| return spirvType; |
| } |
| |
| const SpirvTypeData &SPIRVBuilder::getTypeData(const TType &type, TLayoutBlockStorage blockStorage) |
| { |
| SpirvType spirvType = getSpirvType(type, blockStorage); |
| |
| 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) |
| { |
| 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::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); |
| if (subType.arraySizes.empty() && subType.block == nullptr) |
| { |
| subType.blockStorage = EbsUnspecified; |
| } |
| |
| const spirv::IdRef subTypeId = getSpirvTypeData(subType, block).id; |
| |
| const unsigned int length = type.arraySizes.back(); |
| typeId = getNewId({}); |
| |
| if (length == 0) |
| { |
| // Storage buffers may include a dynamically-sized array, which is identified by it |
| // having a length of 0. |
| spirv::WriteTypeRuntimeArray(&mSpirvTypeAndConstantDecls, typeId, subTypeId); |
| } |
| else |
| { |
| const spirv::IdRef lengthId = getUintConstant(length); |
| 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(); |
| SpirvType fieldSpirvType = getSpirvType(fieldType, type.blockStorage); |
| const TSymbol *structure = fieldType.getStruct(); |
| // Propagate invariant to struct members. |
| if (structure != nullptr) |
| { |
| fieldSpirvType.isInvariant = type.isInvariant; |
| } |
| |
| spirv::IdRef fieldTypeId = getSpirvTypeData(fieldSpirvType, structure).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; |
| imageType.blockStorage = EbsUnspecified; |
| |
| const spirv::IdRef nonSampledId = getSpirvTypeData(imageType, nullptr).id; |
| |
| typeId = getNewId({}); |
| spirv::WriteTypeSampledImage(&mSpirvTypeAndConstantDecls, typeId, nonSampledId); |
| } |
| else if (IsImage(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); |
| spv::ImageFormat imageFormat = getImageFormat(type.imageInternalFormat); |
| |
| typeId = getNewId({}); |
| spirv::WriteTypeImage(&mSpirvTypeAndConstantDecls, typeId, sampledType, dim, depth, arrayed, |
| multisampled, sampled, imageFormat, nullptr); |
| } |
| else if (IsSubpassInputType(type.type)) |
| { |
| // TODO: add support for framebuffer fetch. http://anglebug.com/4889 |
| UNIMPLEMENTED(); |
| } |
| 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.blockStorage = EbsUnspecified; |
| |
| 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.blockStorage = EbsUnspecified; |
| |
| 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/4889 |
| 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: |
| // TODO: In SPIR-V, it's invalid to have a bool type in an interface block. An AST |
| // transformation should be written to rewrite the blocks to use a uint type with |
| // appropriate casts where used. Need to handle: |
| // |
| // - Store: cast the rhs of assignment |
| // - Non-array load: cast the expression |
| // - Array load (for example to use in a struct constructor): reconstruct the array |
| // with elements cast. |
| // - Pass to function as out parameter: Use |
| // MonomorphizeUnsupportedFunctionsInVulkanGLSL to avoid it, as there's no easy |
| // way to handle such function calls inside if conditions and such. |
| // |
| // It might be simplest to do this for bools in structs as well, to avoid having to |
| // convert between an old and new struct type if the struct is used both inside and |
| // outside an interface block. |
| // |
| // http://anglebug.com/4889. |
| 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 (type.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()); |
| } |
| } |
| |
| uint32_t baseAlignment = 4; |
| uint32_t sizeInStorageBlock = 0; |
| |
| // Calculate base alignment and sizes for types. Size for blocks are not calculated, as they |
| // are done later at the same time Offset decorations are written. |
| const bool isOpaqueType = IsOpaqueType(type.type); |
| if (!isOpaqueType) |
| { |
| baseAlignment = calculateBaseAlignmentAndSize(type, &sizeInStorageBlock); |
| } |
| |
| // Write decorations for interface block fields. |
| if (type.blockStorage != EbsUnspecified) |
| { |
| if (!isOpaqueType && !type.arraySizes.empty() && type.block == nullptr) |
| { |
| // Write the ArrayStride decoration for arrays inside interface blocks. |
| spirv::WriteDecorate( |
| &mSpirvDecorations, typeId, spv::DecorationArrayStride, |
| {spirv::LiteralInteger(sizeInStorageBlock / GetOutermostArraySize(type))}); |
| } |
| else if (type.arraySizes.empty() && type.block != nullptr) |
| { |
| // Write the Offset decoration for interface blocks and structs in them. |
| sizeInStorageBlock = calculateSizeAndWriteOffsetDecorations(type, typeId); |
| } |
| } |
| |
| // Write other member decorations. |
| if (type.block != nullptr && type.arraySizes.empty()) |
| { |
| writeMemberDecorations(type, typeId); |
| } |
| |
| return {typeId, baseAlignment, sizeInStorageBlock}; |
| } |
| |
| 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 = 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 EbtSamplerExternalOES: |
| case EbtSamplerExternal2DY2YEXT: |
| case EbtSamplerVideoWEBGL: |
| break; |
| case EbtSampler2DArray: |
| case EbtImage2DArray: |
| isArrayed = true; |
| break; |
| case EbtSampler2DMS: |
| case EbtImage2DMS: |
| 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: |
| sampledType = EbtInt; |
| break; |
| case EbtISampler2DArray: |
| case EbtIImage2DArray: |
| sampledType = EbtInt; |
| isArrayed = true; |
| break; |
| case EbtISampler2DMS: |
| case EbtIImage2DMS: |
| sampledType = EbtInt; |
| isMultisampled = true; |
| break; |
| case EbtISampler2DMSArray: |
| case EbtIImage2DMSArray: |
| sampledType = EbtInt; |
| isArrayed = true; |
| isMultisampled = true; |
| break; |
| |
| // Unsinged integer 2D images |
| case EbtUSampler2D: |
| case EbtUImage2D: |
| sampledType = EbtUInt; |
| break; |
| case EbtUSampler2DArray: |
| case EbtUImage2DArray: |
| sampledType = EbtUInt; |
| isArrayed = true; |
| break; |
| case EbtUSampler2DMS: |
| case EbtUImage2DMS: |
| 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: |
| // TODO: support framebuffer fetch. http://anglebug.com/4889 |
| 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 |
| // |
| // 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; |
| default: |
| // TODO: support framebuffer fetch. http://anglebug.com/4889 |
| 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()) |
| { |
| 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)); |
| |
| iter = constants->insert({value, constantId}).first; |
| } |
| |
| return iter->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::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) |
| { |
| const spirv::IdRef valueId = getUintConstant(value); |
| return getVectorConstantHelper(valueId, EbtUInt, size); |
| } |
| |
| spirv::IdRef SPIRVBuilder::getIvecConstant(int32_t value, int size) |
| { |
| const spirv::IdRef valueId = getIntConstant(value); |
| return getVectorConstantHelper(valueId, EbtInt, size); |
| } |
| |
| spirv::IdRef SPIRVBuilder::getVecConstant(float value, int 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 |
| { |
| // The Invariant decoration is applied to output variables if specified or if globally enabled. |
| return type.isInvariant() || |
| (IsShaderOut(type.getQualifier()) && mCompiler->getPragma().stdgl.invariantAll); |
| } |
| |
| 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 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(type.getQualifier()); |
| const bool needsInputAttachmentIndex = IsSubpassInputType(type.getBasicType()); |
| const bool needsBlendIndex = |
| type.getQualifier() == EvqFragmentOut && layoutQualifier.index >= 0; |
| |
| // TODO: handle row-major matrixes. http://anglebug.com/4889. |
| // TODO: handle invariant (spv::DecorationInvariant). |
| |
| // 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)}); |
| } |
| } |
| |
| 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(); |
| } |
| |
| // This function is nearly identical to getTypeData(), except for row-major matrices. For the |
| // purposes of base alignment and size calculations, it swaps the primary and secondary sizes such |
| // that the look up always assumes column-major matrices. Row-major matrices are only applicable to |
| // interface block fields, so this function is only called on those. |
| const SpirvTypeData &SPIRVBuilder::getFieldTypeDataForAlignmentAndSize( |
| const TType &type, |
| TLayoutBlockStorage blockStorage) |
| { |
| SpirvType fieldSpirvType = getSpirvType(type, blockStorage); |
| |
| // If the field is row-major, swap the rows and columns for the purposes of base alignment |
| // calculation. |
| const bool isRowMajor = type.getLayoutQualifier().matrixPacking == EmpRowMajor; |
| if (isRowMajor) |
| { |
| std::swap(fieldSpirvType.primarySize, fieldSpirvType.secondarySize); |
| } |
| |
| return getSpirvTypeData(fieldSpirvType, nullptr); |
| } |
| |
| uint32_t SPIRVBuilder::calculateBaseAlignmentAndSize(const SpirvType &type, |
| uint32_t *sizeInStorageBlockOut) |
| { |
| // Calculate the base alignment of a type according to the rules of std140 and std430 packing. |
| // |
| // See GLES3.2 Section 7.6.2.2 Standard Uniform Block Layout. |
| |
| if (!type.arraySizes.empty()) |
| { |
| // > Rule 4. If the member is an array of scalars or vectors, the base alignment and array |
| // > stride are set to match the base alignment of a single array element, according to |
| // > rules (1), (2), and (3), ... |
| // |
| // > Rule 10. If the member is an array of S structures, the S elements of the array are |
| // > laid out in order, according to rule (9). |
| SpirvType baseType = type; |
| baseType.arraySizes = {}; |
| if (baseType.arraySizes.empty() && baseType.block == nullptr) |
| { |
| baseType.blockStorage = EbsUnspecified; |
| } |
| |
| const SpirvTypeData &baseTypeData = getSpirvTypeData(baseType, nullptr); |
| uint32_t baseAlignment = baseTypeData.baseAlignment; |
| uint32_t baseSizeInStorageBlock = baseTypeData.sizeInStorageBlock; |
| |
| // For std140 only: |
| // > Rule 4. ... and rounded up to the base alignment of a vec4. |
| // > Rule 9. ... If none of the structure members are larger than a vec4, the base alignment |
| // of the structure is vec4. |
| if (type.blockStorage != EbsStd430) |
| { |
| baseAlignment = std::max(baseAlignment, 16u); |
| baseSizeInStorageBlock = std::max(baseSizeInStorageBlock, 16u); |
| } |
| // Note that matrix arrays follow a similar rule (rules 6 and 8). The matrix base alignment |
| // is the same as its column or row base alignment, and arrays of that matrix don't change |
| // the base alignment. |
| |
| // The size occupied by the array is simply the size of each element (which is already |
| // aligned to baseAlignment) multiplied by the number of elements. |
| *sizeInStorageBlockOut = baseSizeInStorageBlock * GetTotalArrayElements(type); |
| |
| return baseAlignment; |
| } |
| |
| if (type.block != nullptr) |
| { |
| // > Rule 9. If the member is a structure, the base alignment of the structure is N, where N |
| // > is the largest base alignment value of any of its members, and rounded up to the base |
| // > alignment of a vec4. |
| |
| uint32_t baseAlignment = 4; |
| for (const TField *field : type.block->fields()) |
| { |
| const SpirvTypeData &fieldTypeData = |
| getFieldTypeDataForAlignmentAndSize(*field->type(), type.blockStorage); |
| baseAlignment = std::max(baseAlignment, fieldTypeData.baseAlignment); |
| } |
| |
| // For std140 only: |
| // > If none of the structure members are larger than a vec4, the base alignment of the |
| // structure is vec4. |
| if (type.blockStorage != EbsStd430) |
| { |
| baseAlignment = std::max(baseAlignment, 16u); |
| } |
| |
| // Note: sizeInStorageBlockOut is not calculated here, it's done in |
| // calculateSizeAndWriteOffsetDecorations at the same time offsets are calculated. |
| *sizeInStorageBlockOut = 0; |
| |
| return baseAlignment; |
| } |
| |
| if (type.secondarySize > 1) |
| { |
| SpirvType vectorType = type; |
| |
| // > Rule 5. If the member is a column-major matrix with C columns and R rows, the matrix is |
| // > stored identically to an array of C column vectors with R components each, according to |
| // > rule (4). |
| // |
| // > Rule 7. If the member is a row-major matrix with C columns and R rows, the matrix is |
| // > stored identically to an array of R row vectors with C components each, according to |
| // > rule (4). |
| // |
| // For example, given a mat3x4 (3 columns, 4 rows), the base alignment is the same as the |
| // base alignment of a vec4 (secondary size) if column-major, and a vec3 (primary size) if |
| // row-major. |
| // |
| // Here, we always calculate the base alignment and size for column-major matrices. If a |
| // row-major matrix is used in a block, the columns and rows are simply swapped before |
| // looking up the base alignment and size. |
| |
| vectorType.primarySize = vectorType.secondarySize; |
| vectorType.secondarySize = 1; |
| |
| const SpirvTypeData &vectorTypeData = getSpirvTypeData(vectorType, nullptr); |
| uint32_t baseAlignment = vectorTypeData.baseAlignment; |
| |
| // For std140 only: |
| // > Rule 4. ... and rounded up to the base alignment of a vec4. |
| if (type.blockStorage != EbsStd430) |
| { |
| baseAlignment = std::max(baseAlignment, 16u); |
| } |
| |
| // The size occupied by the matrix is the size of each vector multiplied by the number of |
| // vectors. |
| *sizeInStorageBlockOut = vectorTypeData.sizeInStorageBlock * vectorType.primarySize; |
| |
| return baseAlignment; |
| } |
| |
| if (type.primarySize > 1) |
| { |
| // > Rule 2. If the member is a two- or four-component vector with components consuming N |
| // > basic machine units, the base alignment is 2N or 4N, respectively. |
| // |
| // > Rule 3. If the member is a three-component vector with components consuming N basic |
| // > machine units, the base alignment is 4N. |
| |
| SpirvType baseType = type; |
| baseType.primarySize = 1; |
| |
| const SpirvTypeData &baseTypeData = getSpirvTypeData(baseType, nullptr); |
| uint32_t baseAlignment = baseTypeData.baseAlignment; |
| |
| uint32_t multiplier = type.primarySize != 3 ? type.primarySize : 4; |
| baseAlignment *= multiplier; |
| |
| // The size occupied by the vector is the same as its alignment. |
| *sizeInStorageBlockOut = baseAlignment; |
| |
| return baseAlignment; |
| } |
| |
| // TODO: support desktop GLSL. http://anglebug.com/4889. Except for double (desktop GLSL), |
| // every other type occupies 4 bytes. |
| constexpr uint32_t kBasicAlignment = 4; |
| *sizeInStorageBlockOut = kBasicAlignment; |
| return kBasicAlignment; |
| } |
| |
| uint32_t SPIRVBuilder::calculateSizeAndWriteOffsetDecorations(const SpirvType &type, |
| spirv::IdRef typeId) |
| { |
| ASSERT(type.block != nullptr); |
| |
| uint32_t fieldIndex = 0; |
| uint32_t nextOffset = 0; |
| |
| // Get the storage size for each field, align them based on block storage rules, and sum them |
| // up. In the process, write Offset decorations for the block. |
| // |
| // See GLES3.2 Section 7.6.2.2 Standard Uniform Block Layout. |
| |
| for (const TField *field : type.block->fields()) |
| { |
| const TType &fieldType = *field->type(); |
| |
| // Round the offset up to the field's alignment. The spec says: |
| // |
| // > A structure and each structure member have a base offset and a base alignment, from |
| // > which an aligned offset is computed by rounding the base offset up to a multiple of the |
| // > base alignment. |
| const SpirvTypeData &fieldTypeData = |
| getFieldTypeDataForAlignmentAndSize(fieldType, type.blockStorage); |
| nextOffset = rx::roundUp(nextOffset, fieldTypeData.baseAlignment); |
| |
| // Write the Offset decoration. |
| spirv::WriteMemberDecorate(&mSpirvDecorations, typeId, spirv::LiteralInteger(fieldIndex), |
| spv::DecorationOffset, {spirv::LiteralInteger(nextOffset)}); |
| |
| // Calculate the next offset. The next offset is the current offset plus the size of the |
| // field, aligned to its base alignment. |
| // |
| // > Rule 4. ... the base offset of the member following the array is rounded up to the next |
| // > multiple of the base alignment. |
| // |
| // > Rule 9. ... the base offset of the member following the sub-structure is rounded up to |
| // > the next multiple of the base alignment of the structure. |
| nextOffset = nextOffset + fieldTypeData.sizeInStorageBlock; |
| nextOffset = rx::roundUp(nextOffset, fieldTypeData.baseAlignment); |
| |
| ++fieldIndex; |
| } |
| |
| return nextOffset; |
| } |
| |
| 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(); |
| const SpirvTypeData &fieldTypeData = |
| getFieldTypeDataForAlignmentAndSize(fieldType, type.blockStorage); |
| |
| // Add invariant decoration if any. |
| if (type.isInvariant || fieldType.isInvariant()) |
| { |
| spirv::WriteMemberDecorate(&mSpirvDecorations, typeId, |
| spirv::LiteralInteger(fieldIndex), spv::DecorationInvariant, |
| {}); |
| } |
| |
| // Add matrix decorations if any. |
| if (fieldType.isMatrix()) |
| { |
| // The matrix stride is simply the alignment of the vector constituting a column or row. |
| const uint32_t matrixStride = fieldTypeData.baseAlignment; |
| |
| // MatrixStride |
| spirv::WriteMemberDecorate( |
| &mSpirvDecorations, typeId, spirv::LiteralInteger(fieldIndex), |
| spv::DecorationMatrixStride, {spirv::LiteralInteger(matrixStride)}); |
| |
| // ColMajor or RowMajor |
| const bool isRowMajor = fieldType.getLayoutQualifier().matrixPacking == EmpRowMajor; |
| spirv::WriteMemberDecorate( |
| &mSpirvDecorations, typeId, spirv::LiteralInteger(fieldIndex), |
| isRowMajor ? spv::DecorationRowMajor : spv::DecorationColMajor, {}); |
| } |
| |
| // Add other decorations. |
| SpirvDecorations decorations = getDecorations(fieldType); |
| for (const spv::Decoration decoration : decorations) |
| { |
| spirv::WriteMemberDecorate(&mSpirvDecorations, typeId, |
| spirv::LiteralInteger(fieldIndex), decoration, {}); |
| } |
| |
| ++fieldIndex; |
| } |
| } |
| |
| 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. The Shader capability is always defined. |
| spirv::WriteCapability(&result, spv::CapabilityShader); |
| 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, {}); |
| 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 |