blob: cdf7292ba8cacc1f82fa26ca5b17588b531dd72e [file] [log] [blame]
//
// 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 &paramTypeIds)
{
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