Commit aa2626f9 by Shahbaz Youssefi Committed by Angle LUCI CQ

Vulkan: SPIR-V Gen: Handle load/store access chain

This change implements a key instruction of SPIR-V, OpAccessChain. Inspired by glslang's implementation, a class is added (AccessChain) that tracks "indices" into a base value. These indices could select a field of a block, an element of an array, a column of a matrix, or a component of a vector. Nuances (such as multi-component swizzle as an lvalue not representable in SPIR-V) and optimizations (such as all-literal indices to an rvalue) that are implemented in glslang have also been implemented in this change. As a result, this change implements all manners of loads and stores (with the exception of this gotcha: https://github.com/KhronosGroup/glslang/issues/2637). The change uses this feature to translate the basic shader which does `gl_Position = positionAttr;` (by implementing EOpAssign), and enables a test that uses this shader. Bug: angleproject:4889 Change-Id: I22dbe5b169ce499eaac657902164aca3b0ebc193 Reviewed-on: https://chromium-review.googlesource.com/c/angle/angle/+/2911880 Commit-Queue: Shahbaz Youssefi <syoussefi@chromium.org> Reviewed-by: 's avatarJamie Madill <jmadill@chromium.org> Reviewed-by: 's avatarTim Van Patten <timvp@google.com>
parent 152450f8
......@@ -1086,6 +1086,11 @@ inline bool IsShaderOut(TQualifier qualifier)
case EvqSampleOut:
case EvqPatchOut:
case EvqFragmentInOut:
// Per-vertex built-ins when used without gl_in or gl_out are always output.
case EvqPosition:
case EvqPointSize:
case EvqClipDistance:
case EvqCullDistance:
return true;
default:
return false;
......
......@@ -293,7 +293,7 @@ SpirvTypeData SPIRVBuilder::declareType(const SpirvType &type, const char *block
// 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)
if (type.block != nullptr && type.arraySizes.empty())
{
spirv::WriteName(&mSpirvDebug, typeId, blockName);
......@@ -912,6 +912,10 @@ uint32_t SPIRVBuilder::calculateBaseAlignmentAndSize(const SpirvType &type,
// > 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, "");
uint32_t baseAlignment = baseTypeData.baseAlignment;
......@@ -1173,6 +1177,12 @@ spirv::Blob SPIRVBuilder::getSpirv()
}
result.insert(result.end(), mSpirvExecutionModes.begin(), mSpirvExecutionModes.end());
// - 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());
......
......@@ -177,6 +177,8 @@ class SPIRVBuilder : angle::NonCopyable
spirv::Blob *getSpirvExecutionModes() { return &mSpirvExecutionModes; }
spirv::Blob *getSpirvDebug() { return &mSpirvDebug; }
spirv::Blob *getSpirvDecorations() { return &mSpirvDecorations; }
spirv::Blob *getSpirvTypeAndConstantDecls() { return &mSpirvTypeAndConstantDecls; }
spirv::Blob *getSpirvTypePointerDecls() { return &mSpirvTypePointerDecls; }
spirv::Blob *getSpirvVariableDecls() { return &mSpirvVariableDecls; }
spirv::Blob *getSpirvFunctions() { return &mSpirvFunctions; }
......@@ -190,6 +192,12 @@ class SPIRVBuilder : angle::NonCopyable
uint32_t calculateBaseAlignmentAndSize(const SpirvType &type, uint32_t *sizeInStorageBlockOut);
uint32_t calculateSizeAndWriteOffsetDecorations(const SpirvType &type, spirv::IdRef typeId);
spirv::IdRef getBoolConstant(bool value);
spirv::IdRef getUintConstant(uint32_t value);
spirv::IdRef getIntConstant(int32_t value);
spirv::IdRef getFloatConstant(float value);
spirv::IdRef getCompositeConstant(spirv::IdRef typeId, const spirv::IdRefList &values);
// TODO: remove name hashing once translation through glslang is removed. That is necessary to
// avoid name collision between ANGLE's internal symbols and user-defined ones when compiling
// the generated GLSL, but is irrelevant when generating SPIR-V directly. Currently, the SPIR-V
......@@ -215,14 +223,9 @@ class SPIRVBuilder : angle::NonCopyable
spirv::LiteralInteger *sampledOut);
spv::ImageFormat getImageFormat(TLayoutImageInternalFormat imageInternalFormat);
spirv::IdRef getBoolConstant(bool value);
spirv::IdRef getBasicConstantHelper(uint32_t value,
TBasicType type,
angle::HashMap<uint32_t, spirv::IdRef> *constants);
spirv::IdRef getUintConstant(uint32_t value);
spirv::IdRef getIntConstant(int32_t value);
spirv::IdRef getFloatConstant(float value);
spirv::IdRef getCompositeConstant(spirv::IdRef typeId, const spirv::IdRefList &values);
uint32_t nextUnusedBinding();
uint32_t nextUnusedInputLocation(uint32_t consumedCount);
......
......@@ -30,6 +30,93 @@ namespace sh
{
namespace
{
// A struct to hold either SPIR-V ids or literal constants. If id is not valid, a literal is
// assumed.
struct SpirvIdOrLiteral
{
SpirvIdOrLiteral() = default;
SpirvIdOrLiteral(const spirv::IdRef idIn) : id(idIn) {}
SpirvIdOrLiteral(const spirv::LiteralInteger literalIn) : literal(literalIn) {}
spirv::IdRef id;
spirv::LiteralInteger literal;
};
// A data structure to facilitate generating array indexing, block field selection, swizzle and
// such. Used in conjunction with NodeData which includes the access chain's baseId and idList.
//
// - rvalue[literal].field[literal] generates OpCompositeExtract
// - rvalue.x generates OpCompositeExtract
// - rvalue.xyz generates OpVectorShuffle
// - rvalue.xyz[i] generates OpVectorExtractDynamic (xyz[i] itself generates an
// OpVectorExtractDynamic as well)
// - rvalue[i].field[j] generates a temp variable OpStore'ing rvalue and then generating an
// OpAccessChain and OpLoad
//
// - lvalue[i].field[j].x generates OpAccessChain and OpStore
// - lvalue.xyz generates an OpLoad followed by OpVectorShuffle and OpStore
// - lvalue.xyz[i] generates OpAccessChain and OpStore (xyz[i] itself generates an
// OpVectorExtractDynamic as well)
//
// storageClass == Max implies an rvalue.
//
struct AccessChain
{
// The storage class for lvalues. If Max, it's an rvalue.
spv::StorageClass storageClass = spv::StorageClassMax;
// If the access chain ends in swizzle, the swizzle components are specified here. Swizzles
// select multiple components so need special treatment when used as lvalue.
std::vector<uint32_t> swizzles;
// If a vector component is selected dynamically (i.e. indexed with a non-literal index),
// dynamicComponent will contain the id of the index.
spirv::IdRef dynamicComponent;
// Type of expression before swizzle is applied, after swizzle is applied and after dynamic
// component is applied.
spirv::IdRef preSwizzleTypeId;
spirv::IdRef postSwizzleTypeId;
spirv::IdRef postDynamicComponentTypeId;
// If the OpAccessChain is already generated (done by accessChainCollapse()), this caches the
// id.
spirv::IdRef accessChainId;
// Whether all indices are literal. Avoids looping through indices to determine this
// information.
bool areAllIndicesLiteral = true;
// The number of components in the vector, if vector and swizzle is used. This is cached to
// avoid a type look up when handling swizzles.
uint8_t swizzledVectorComponentCount = 0;
// The block storage of the base id. Used to correctly select the SPIR-V type id when visiting
// EOpIndex* binary nodes.
TLayoutBlockStorage baseBlockStorage;
};
bool IsAccessChainRValue(const AccessChain &accessChain)
{
return accessChain.storageClass == spv::StorageClassMax;
}
// As each node is traversed, it produces data. When visiting back the parent, this data is used to
// complete the data of the parent. For example, the children of a function call (i.e. the
// arguments) each produce a SPIR-V id corresponding to the result of their expression. The
// function call node itself in PostVisit uses those ids to generate the function call instruction.
struct NodeData
{
// An id whose meaning depends on the node. It could be a temporary id holding the result of an
// expression, a reference to a variable etc.
spirv::IdRef baseId;
// List of relevant SPIR-V ids accumulated while traversing the children. Meaning depends on
// the node, for example a list of parameters to be passed to a function, a set of ids used to
// construct an access chain etc.
std::vector<SpirvIdOrLiteral> idList;
// For constructing access chains.
AccessChain accessChain;
};
// A traverser that generates SPIR-V as it walks the AST.
class OutputSPIRVTraverser : public TIntermTraverser
{
public:
......@@ -59,10 +146,49 @@ class OutputSPIRVTraverser : public TIntermTraverser
void visitPreprocessorDirective(TIntermPreprocessorDirective *node) override;
private:
// Access chain handling.
void accessChainPush(NodeData *data, spirv::IdRef index, spirv::IdRef typeId) const;
void accessChainPushLiteral(NodeData *data,
spirv::LiteralInteger index,
spirv::IdRef typeId) const;
void accessChainPushSwizzle(NodeData *data,
const TVector<int> &swizzle,
spirv::IdRef typeId,
uint8_t componentCount) const;
void accessChainPushDynamicComponent(NodeData *data, spirv::IdRef index, spirv::IdRef typeId);
spirv::IdRef accessChainCollapse(NodeData *data);
spirv::IdRef accessChainLoad(NodeData *data);
void accessChainStore(NodeData *data, spirv::IdRef value);
// Access chain helpers.
void makeAccessChainIdList(NodeData *data, spirv::IdRefList *idsOut);
void makeAccessChainLiteralList(NodeData *data, spirv::LiteralIntegerList *literalsOut);
spirv::IdRef getAccessChainTypeId(NodeData *data);
// Node data handling.
void nodeDataInitLValue(NodeData *data,
spirv::IdRef baseId,
spirv::IdRef typeId,
spv::StorageClass storageClass,
TLayoutBlockStorage blockStorage) const;
void nodeDataInitRValue(NodeData *data, spirv::IdRef baseId, spirv::IdRef typeId) const;
ANGLE_MAYBE_UNUSED TCompiler *mCompiler;
ANGLE_MAYBE_UNUSED ShCompileOptions mCompileOptions;
SPIRVBuilder mBuilder;
// Traversal state. Nodes generally push() once to this stack on PreVisit. On InVisit and
// PostVisit, they pop() once (data corresponding to the result of the child) and accumulate it
// in back() (data corresponding to the node itself). On PostVisit, code is generated.
std::vector<NodeData> mNodeData;
// A map of TSymbol to its SPIR-V id. This could be a:
//
// - TVariable, or
// - TInterfaceBlock: because TIntermSymbols referencing a field of an unnamed interface block
// don't reference the TVariable that defines the struct, but the TInterfaceBlock itself.
angle::HashMap<const TSymbol *, spirv::IdRef> mSymbolIdMap;
};
spv::StorageClass GetStorageClass(const TType &type)
......@@ -119,10 +245,429 @@ OutputSPIRVTraverser::OutputSPIRVTraverser(TCompiler *compiler, ShCompileOptions
compiler->getNameMap())
{}
void OutputSPIRVTraverser::nodeDataInitLValue(NodeData *data,
spirv::IdRef baseId,
spirv::IdRef typeId,
spv::StorageClass storageClass,
TLayoutBlockStorage blockStorage) const
{
*data = {};
// Initialize the access chain as an lvalue. Useful when an access chain is resolved, but needs
// to be replaced by a reference to a temporary variable holding the result.
data->baseId = baseId;
data->accessChain.preSwizzleTypeId = typeId;
data->accessChain.storageClass = storageClass;
data->accessChain.baseBlockStorage = blockStorage;
}
void OutputSPIRVTraverser::nodeDataInitRValue(NodeData *data,
spirv::IdRef baseId,
spirv::IdRef typeId) const
{
*data = {};
// Initialize the access chain as an rvalue. Useful when an access chain is resolved, and needs
// to be replaced by a reference to it.
data->baseId = baseId;
data->accessChain.preSwizzleTypeId = typeId;
}
void OutputSPIRVTraverser::accessChainPush(NodeData *data,
spirv::IdRef index,
spirv::IdRef typeId) const
{
// Simply add the index to the chain of indices.
data->idList.emplace_back(index);
data->accessChain.areAllIndicesLiteral = false;
data->accessChain.preSwizzleTypeId = typeId;
}
void OutputSPIRVTraverser::accessChainPushLiteral(NodeData *data,
spirv::LiteralInteger index,
spirv::IdRef typeId) const
{
// Add the literal integer in the chain of indices. Since this is an id list, fake it as an id.
data->idList.emplace_back(index);
data->accessChain.preSwizzleTypeId = typeId;
}
void OutputSPIRVTraverser::accessChainPushSwizzle(NodeData *data,
const TVector<int> &swizzle,
spirv::IdRef typeId,
uint8_t componentCount) const
{
AccessChain &accessChain = data->accessChain;
// Record the swizzle as multi-component swizzles require special handling. When loading
// through the access chain, the swizzle is applied after loading the vector first (see
// |accessChainLoad()|). When storing through the access chain, the whole vector is loaded,
// swizzled components overwritten and the whoel vector written back (see |accessChainStore()|).
ASSERT(accessChain.swizzles.empty());
if (swizzle.size() == 1)
{
// If this swizzle is selecting a single component, fold it into the access chain.
accessChainPushLiteral(data, spirv::LiteralInteger(swizzle[0]), typeId);
}
else
{
// Otherwise keep them separate.
accessChain.swizzles.insert(accessChain.swizzles.end(), swizzle.begin(), swizzle.end());
accessChain.postSwizzleTypeId = typeId;
accessChain.swizzledVectorComponentCount = componentCount;
}
}
void OutputSPIRVTraverser::accessChainPushDynamicComponent(NodeData *data,
spirv::IdRef index,
spirv::IdRef typeId)
{
AccessChain &accessChain = data->accessChain;
// Record the index used to dynamically select a component of a vector.
ASSERT(!accessChain.dynamicComponent.valid());
if (IsAccessChainRValue(accessChain) && accessChain.areAllIndicesLiteral)
{
// If the access chain is an rvalue with all-literal indices, keep this index separate so
// that OpCompositeExtract can be used for the access chain up to this index.
accessChain.dynamicComponent = index;
accessChain.postDynamicComponentTypeId = typeId;
return;
}
if (!accessChain.swizzles.empty())
{
// Otherwise if there's a swizzle, fold the swizzle and dynamic component selection into a
// single dynamic component selection.
ASSERT(accessChain.swizzles.size() > 1);
// Create a vector constant from the swizzles.
spirv::IdRefList swizzleIds;
for (uint32_t component : accessChain.swizzles)
{
swizzleIds.push_back(mBuilder.getUintConstant(component));
}
SpirvType type;
type.type = EbtUInt;
const spirv::IdRef uintTypeId = mBuilder.getSpirvTypeData(type, "").id;
type.primarySize = static_cast<uint8_t>(swizzleIds.size());
const spirv::IdRef uvecTypeId = mBuilder.getSpirvTypeData(type, "").id;
const spirv::IdRef swizzlesId = mBuilder.getNewId();
spirv::WriteConstantComposite(mBuilder.getSpirvTypeAndConstantDecls(), uvecTypeId,
swizzlesId, swizzleIds);
// Index that vector constant with the dynamic index. For example, vec.ywxz[i] becomes the
// constant {1, 3, 0, 2} indexed with i, and that index used on vec.
const spirv::IdRef newIndex = mBuilder.getNewId();
spirv::WriteVectorExtractDynamic(mBuilder.getSpirvFunctions(), uintTypeId, newIndex,
swizzlesId, index);
index = newIndex;
accessChain.swizzles.clear();
}
// Fold it into the access chain.
accessChainPush(data, index, typeId);
}
spirv::IdRef OutputSPIRVTraverser::accessChainCollapse(NodeData *data)
{
AccessChain &accessChain = data->accessChain;
ASSERT(accessChain.storageClass != spv::StorageClassMax);
if (accessChain.accessChainId.valid())
{
return accessChain.accessChainId;
}
// If there are no indices, the baseId is where access is done to/from.
if (data->idList.empty())
{
accessChain.accessChainId = data->baseId;
return accessChain.accessChainId;
}
// Otherwise create an OpAccessChain instruction. Swizzle handling is special as it selects
// multiple components, and is done differently for load and store.
spirv::IdRefList indexIds;
makeAccessChainIdList(data, &indexIds);
const spirv::IdRef typePointerId =
mBuilder.getTypePointerId(accessChain.preSwizzleTypeId, accessChain.storageClass);
accessChain.accessChainId = mBuilder.getNewId();
spirv::WriteAccessChain(mBuilder.getSpirvFunctions(), typePointerId, accessChain.accessChainId,
data->baseId, indexIds);
return accessChain.accessChainId;
}
spirv::IdRef OutputSPIRVTraverser::accessChainLoad(NodeData *data)
{
// Loading through the access chain can generate different instructions based on whether it's an
// rvalue, the indices are literal, there's a swizzle etc.
//
// - If rvalue:
// * With indices:
// + All literal: OpCompositeExtract which uses literal integers to access the rvalue.
// + Otherwise: Can't use OpAccessChain on an rvalue, so create a temporary variable, OpStore
// the rvalue into it, then use OpAccessChain and OpLoad to load from it.
// * Without indices: Take the base id.
// - If lvalue:
// * With indices: Use OpAccessChain and OpLoad
// * Without indices: Use OpLoad
// - With swizzle: Use OpVectorShuffle on the result of the previous step
// - With dynamic component: Use OpVectorExtractDynamic on the result of the previous step
AccessChain &accessChain = data->accessChain;
spirv::IdRef loadResult = data->baseId;
if (IsAccessChainRValue(accessChain))
{
if (data->idList.size() > 0)
{
if (accessChain.areAllIndicesLiteral)
{
// Use OpCompositeExtract on an rvalue with all literal indices.
spirv::LiteralIntegerList indexList;
makeAccessChainLiteralList(data, &indexList);
const spirv::IdRef result = mBuilder.getNewId();
spirv::WriteCompositeExtract(mBuilder.getSpirvFunctions(),
accessChain.preSwizzleTypeId, result, loadResult,
indexList);
loadResult = result;
}
else
{
// Create a temp variable to hold the rvalue so an access chain can be made on it.
// TODO: variables need to be placed at the top of the SPIR-V block. This will be
// fixed when blocks are properly supported. http://anglebug.com/4889
const spirv::IdRef tempVar = mBuilder.getNewId();
spirv::WriteVariable(mBuilder.getSpirvFunctions(), accessChain.preSwizzleTypeId,
tempVar, spv::StorageClassFunction, nullptr);
// Write the rvalue into the temp variable
spirv::WriteStore(mBuilder.getSpirvFunctions(), tempVar, loadResult, nullptr);
// Make the temp variable the source of the access chain.
data->baseId = tempVar;
data->accessChain.storageClass = spv::StorageClassFunction;
// Load from the temp variable.
const spirv::IdRef accessChainId = accessChainCollapse(data);
loadResult = mBuilder.getNewId();
spirv::WriteLoad(mBuilder.getSpirvFunctions(), accessChain.preSwizzleTypeId,
loadResult, accessChainId, nullptr);
}
}
}
else
{
// Load from the access chain.
const spirv::IdRef accessChainId = accessChainCollapse(data);
loadResult = mBuilder.getNewId();
spirv::WriteLoad(mBuilder.getSpirvFunctions(), accessChain.preSwizzleTypeId, loadResult,
accessChainId, nullptr);
}
if (!accessChain.swizzles.empty())
{
// Single-component swizzles are already folded into the index list.
ASSERT(accessChain.swizzles.size() > 1);
// Take the loaded value and use OpVectorShuffle to create the swizzle.
spirv::LiteralIntegerList swizzleList;
for (uint32_t component : accessChain.swizzles)
{
swizzleList.push_back(spirv::LiteralInteger(component));
}
const spirv::IdRef result = mBuilder.getNewId();
spirv::WriteVectorShuffle(mBuilder.getSpirvFunctions(), accessChain.postSwizzleTypeId,
result, loadResult, loadResult, swizzleList);
loadResult = result;
}
if (accessChain.dynamicComponent.valid())
{
// Dynamic component in combination with swizzle is already folded.
ASSERT(accessChain.swizzles.empty());
// Use OpVectorExtractDynamic to select the component.
const spirv::IdRef result = mBuilder.getNewId();
spirv::WriteVectorExtractDynamic(mBuilder.getSpirvFunctions(),
accessChain.postDynamicComponentTypeId, result, loadResult,
accessChain.dynamicComponent);
loadResult = result;
}
return loadResult;
}
void OutputSPIRVTraverser::accessChainStore(NodeData *data, spirv::IdRef value)
{
// Storing through the access chain can generate different instructions based on whether the
// there's a swizzle.
//
// - Without swizzle: Use OpAccessChain and OpStore
// - With swizzle: Use OpAccessChain and OpLoad to load the vector, then use OpVectorShuffle to
// replace the components being overwritten. Finally, use OpStore to write the result back.
AccessChain &accessChain = data->accessChain;
// Single-component swizzles are already folded into the indices.
ASSERT(accessChain.swizzles.size() != 1);
// Since store can only happen through lvalues, it's impossible to have a dynamic component as
// that always gets folded into the indices except for rvalues.
ASSERT(!accessChain.dynamicComponent.valid());
const spirv::IdRef accessChainId = accessChainCollapse(data);
if (!accessChain.swizzles.empty())
{
// Load the vector before the swizzle.
const spirv::IdRef loadResult = mBuilder.getNewId();
spirv::WriteLoad(mBuilder.getSpirvFunctions(), accessChain.preSwizzleTypeId, loadResult,
accessChainId, nullptr);
// Overwrite the components being written. This is done by first creating an identity
// swizzle, then replacing the components being written with a swizzle from the value. For
// example, take the following:
//
// vec4 v;
// v.zx = u;
//
// The OpVectorShuffle instruction takes two vectors (v and u) and selects components from
// each (in this example, swizzles [0, 3] select from v and [4, 7] select from u). This
// algorithm first creates the identity swizzles {0, 1, 2, 3}, then replaces z and x (the
// 0th and 2nd element) with swizzles from u (4 + {0, 1}) to get the result
// {4+1, 1, 4+0, 3}.
spirv::LiteralIntegerList swizzleList;
for (uint32_t component = 0; component < accessChain.swizzledVectorComponentCount;
++component)
{
swizzleList.push_back(spirv::LiteralInteger(component));
}
uint32_t srcComponent = 0;
for (uint32_t dstComponent : accessChain.swizzles)
{
swizzleList[dstComponent] =
spirv::LiteralInteger(accessChain.swizzledVectorComponentCount + srcComponent);
++srcComponent;
}
// Use the generated swizzle to select components from the loaded vector and the value to be
// written. Use the final result as the value to be written to the vector.
const spirv::IdRef result = mBuilder.getNewId();
spirv::WriteVectorShuffle(mBuilder.getSpirvFunctions(), accessChain.postSwizzleTypeId,
result, loadResult, value, swizzleList);
value = result;
}
// Store through the access chain.
spirv::WriteStore(mBuilder.getSpirvFunctions(), accessChainId, value, nullptr);
}
void OutputSPIRVTraverser::makeAccessChainIdList(NodeData *data, spirv::IdRefList *idsOut)
{
for (size_t index = 0; index < data->idList.size(); ++index)
{
spirv::IdRef indexId = data->idList[index].id;
if (!indexId.valid())
{
// The index is a literal integer, so replace it with an OpConstant id.
indexId = mBuilder.getUintConstant(data->idList[index].literal);
}
idsOut->push_back(indexId);
}
}
void OutputSPIRVTraverser::makeAccessChainLiteralList(NodeData *data,
spirv::LiteralIntegerList *literalsOut)
{
for (size_t index = 0; index < data->idList.size(); ++index)
{
ASSERT(!data->idList[index].id.valid());
literalsOut->push_back(data->idList[index].literal);
}
}
spirv::IdRef OutputSPIRVTraverser::getAccessChainTypeId(NodeData *data)
{
// Load and store through the access chain may be done in multiple steps. These steps produce
// the following types:
//
// - preSwizzleTypeId
// - postSwizzleTypeId
// - postDynamicComponentTypeId
//
// The last of these types is the final type of the expression this access chain corresponds to.
const AccessChain &accessChain = data->accessChain;
if (accessChain.postDynamicComponentTypeId.valid())
{
return accessChain.postDynamicComponentTypeId;
}
if (accessChain.postSwizzleTypeId.valid())
{
return accessChain.postSwizzleTypeId;
}
ASSERT(accessChain.preSwizzleTypeId.valid());
return accessChain.preSwizzleTypeId;
}
void OutputSPIRVTraverser::visitSymbol(TIntermSymbol *node)
{
// TODO: http://anglebug.com/4889
UNIMPLEMENTED();
mNodeData.emplace_back();
// The symbol is either:
//
// - A variable (local, varying etc)
// - An interface block
// - A field of an unnamed interface block
const TType &type = node->getType();
const TInterfaceBlock *interfaceBlock = type.getInterfaceBlock();
const TSymbol *symbol = interfaceBlock;
if (interfaceBlock == nullptr)
{
symbol = &node->variable();
}
// Track the block storage; it's needed to determine the derived type in an access chain, but is
// not promoted in intermediate nodes' TType. Defaults to std140.
TLayoutBlockStorage blockStorage = EbsUnspecified;
if (interfaceBlock)
{
blockStorage = type.getLayoutQualifier().blockStorage;
if (!IsShaderIoBlock(type.getQualifier()) && blockStorage != EbsStd430)
{
blockStorage = EbsStd140;
}
}
spirv::IdRef typeId = mBuilder.getTypeData(type, blockStorage).id;
nodeDataInitLValue(&mNodeData.back(), mSymbolIdMap[symbol], typeId, GetStorageClass(type),
blockStorage);
// If a field of a nameless interface block, create an access chain.
if (interfaceBlock && !type.isInterfaceBlock())
{
uint32_t fieldIndex = static_cast<uint32_t>(type.getInterfaceBlockFieldIndex());
accessChainPushLiteral(&mNodeData.back(), spirv::LiteralInteger(fieldIndex), typeId);
}
}
void OutputSPIRVTraverser::visitConstantUnion(TIntermConstantUnion *node)
......@@ -133,16 +678,116 @@ void OutputSPIRVTraverser::visitConstantUnion(TIntermConstantUnion *node)
bool OutputSPIRVTraverser::visitSwizzle(Visit visit, TIntermSwizzle *node)
{
// TODO: http://anglebug.com/4889
UNIMPLEMENTED();
if (visit == PreVisit)
{
// Don't add an entry to the stack. The child will create one, which we won't pop.
return true;
}
ASSERT(visit == PostVisit);
ASSERT(mNodeData.size() >= 1);
const TType &vectorType = node->getOperand()->getType();
const uint8_t vectorComponentCount = static_cast<uint8_t>(vectorType.getNominalSize());
const TVector<int> &swizzle = node->getSwizzleOffsets();
// As an optimization, do nothing if the swizzle is selecting all the components of the vector
// in order.
bool isIdentity = swizzle.size() == vectorComponentCount;
for (size_t index = 0; index < swizzle.size(); ++index)
{
isIdentity = isIdentity && static_cast<size_t>(swizzle[index]) == index;
}
if (isIdentity)
{
return true;
}
const spirv::IdRef typeId =
mBuilder.getTypeData(node->getType(), mNodeData.back().accessChain.baseBlockStorage).id;
accessChainPushSwizzle(&mNodeData.back(), swizzle, typeId, vectorComponentCount);
return true;
}
bool OutputSPIRVTraverser::visitBinary(Visit visit, TIntermBinary *node)
{
// TODO: http://anglebug.com/4889
UNIMPLEMENTED();
if (visit == PreVisit)
{
// Don't add an entry to the stack. The left child will create one, which we won't pop.
return true;
}
if (visit == InVisit)
{
// Left child visited. Take the entry it created as the current node's.
ASSERT(mNodeData.size() >= 1);
// As an optimization, if the index is EOpIndexDirect*, take the constant index directly and
// add it to the access chain as constant.
switch (node->getOp())
{
case EOpIndexDirect:
case EOpIndexDirectStruct:
case EOpIndexDirectInterfaceBlock:
accessChainPushLiteral(
&mNodeData.back(),
spirv::LiteralInteger(node->getRight()->getAsConstantUnion()->getIConst(0)),
mBuilder
.getTypeData(node->getType(), mNodeData.back().accessChain.baseBlockStorage)
.id);
// Don't visit the right child, it's already processed.
return false;
default:
break;
}
return true;
}
// There are at least two entries, one for the left node and one for the right one.
ASSERT(mNodeData.size() >= 2);
// Load the result of the right node right away.
const spirv::IdRef rightTypeId = getAccessChainTypeId(&mNodeData.back());
const spirv::IdRef rightValue = accessChainLoad(&mNodeData.back());
mNodeData.pop_back();
// For EOpIndex* operations, push the right value as an index to the left value's access chain.
// For the other operations, evaluate the expression.
NodeData &left = mNodeData.back();
spirv::IdRef typeId;
switch (node->getOp())
{
case EOpIndexDirect:
case EOpIndexDirectStruct:
case EOpIndexDirectInterfaceBlock:
UNREACHABLE();
break;
case EOpIndexIndirect:
typeId = mBuilder.getTypeData(node->getType(), left.accessChain.baseBlockStorage).id;
if (!node->getLeft()->getType().isArray() && node->getLeft()->getType().isVector())
{
accessChainPushDynamicComponent(&left, rightValue, typeId);
}
else
{
accessChainPush(&left, rightValue, typeId);
}
break;
case EOpAssign:
// Store into the access chain. Since the result of the (a = b) expression is b, change
// the access chain to an unindexed rvalue which is |rightValue|.
accessChainStore(&left, rightValue);
nodeDataInitRValue(&left, rightValue, rightTypeId);
break;
default:
UNIMPLEMENTED();
break;
}
return true;
}
......@@ -189,22 +834,32 @@ bool OutputSPIRVTraverser::visitCase(Visit visit, TIntermCase *node)
bool OutputSPIRVTraverser::visitBlock(Visit visit, TIntermBlock *node)
{
// If global block, nothing to generate.
// If global block, nothing to do.
if (getCurrentTraversalDepth() == 0)
{
return true;
}
// When starting the block, generate an OpLabel instruction. This is referenced by instructions
// that reference the block such as OpBranchConditional.
if (visit == PreVisit)
{
mNodeData.emplace_back();
const spirv::IdRef blockLabelId = mBuilder.getNewId();
spirv::WriteLabel(mBuilder.getSpirvFunctions(), blockLabelId);
}
// TODO: http://anglebug.com/4889
UNIMPLEMENTED();
mNodeData.back().baseId = blockLabelId;
}
else
{
// Any node that needed to generate code has already done so, just clean up its data. If
// the child node has no effect, it's automatically discarded (such as variable.field[n].x,
// side effects of n already having generated code).
mNodeData.pop_back();
}
return false;
return true;
}
bool OutputSPIRVTraverser::visitFunctionDefinition(Visit visit, TIntermFunctionDefinition *node)
......@@ -301,16 +956,17 @@ bool OutputSPIRVTraverser::visitDeclaration(Visit visit, TIntermDeclaration *nod
// Enforced by ValidateASTOptions::validateMultiDeclarations.
ASSERT(sequence.size() == 1);
TIntermTyped *declVariable = sequence.front()->getAsTyped();
const TType &type = declVariable->getType();
TIntermSymbol *symbol = declVariable->getAsSymbolNode();
TIntermSymbol *symbol = sequence.front()->getAsSymbolNode();
ASSERT(symbol != nullptr);
const TType &type = symbol->getType();
const TVariable *variable = &symbol->variable();
// If this is just a struct declaration (and not a variable declaration), don't declare the
// struct up-front and let it be lazily defined. If the struct is only used inside an interface
// block for example, this avoids it being doubly defined (once with the unspecified block
// storage and once with interface block's).
if (type.isStructSpecifier() && symbol->variable().symbolType() == SymbolType::Empty)
if (type.isStructSpecifier() && variable->symbolType() == SymbolType::Empty)
{
return false;
}
......@@ -330,9 +986,6 @@ bool OutputSPIRVTraverser::visitDeclaration(Visit visit, TIntermDeclaration *nod
// TODO: handle initializers. http://anglebug.com/4889
spirv::WriteVariable(spirvSection, typePointerId, variableId, storageClass, nullptr);
// TODO: create a TVariable to variableId map so references to this variable can discover the
// ID. http://anglebug.com/4889
if (IsShaderIn(type.getQualifier()) || IsShaderOut(type.getQualifier()))
{
// Add in and out variables to the list of interface variables.
......@@ -362,8 +1015,18 @@ bool OutputSPIRVTraverser::visitDeclaration(Visit visit, TIntermDeclaration *nod
mBuilder.writeInterfaceVariableDecorations(type, variableId);
// Output debug information.
spirv::WriteName(mBuilder.getSpirvDebug(), variableId,
mBuilder.hashName(&symbol->variable()).data());
spirv::WriteName(mBuilder.getSpirvDebug(), variableId, mBuilder.hashName(variable).data());
// Remember the id of the variable for future look up. For interface blocks, also remember the
// id of the interface block.
ASSERT(mSymbolIdMap.count(variable) == 0);
mSymbolIdMap[variable] = variableId;
if (type.isInterfaceBlock())
{
ASSERT(mSymbolIdMap.count(type.getInterfaceBlock()) == 0);
mSymbolIdMap[type.getInterfaceBlock()] = variableId;
}
return false;
}
......
......@@ -1340,7 +1340,10 @@ bool TranslatorVulkan::translate(TIntermBlock *root,
}
#if defined(ANGLE_ENABLE_DIRECT_SPIRV_GENERATION)
if ((compileOptions & SH_GENERATE_SPIRV_DIRECTLY) != 0 && getShaderType() == GL_VERTEX_SHADER)
constexpr ShCompileOptions kUnsupportedTransformations =
SH_ADD_VULKAN_XFB_EMULATION_SUPPORT_CODE | SH_ADD_BRESENHAM_LINE_RASTER_EMULATION;
if ((compileOptions & SH_GENERATE_SPIRV_DIRECTLY) != 0 && getShaderType() == GL_VERTEX_SHADER &&
(compileOptions & kUnsupportedTransformations) == 0)
{
// Declare the implicitly defined gl_PerVertex I/O blocks if not already. This will help
// SPIR-V generation treat them mostly like usual I/O blocks.
......
......@@ -73,6 +73,6 @@ TEST_P(ColorMaskTest, AMDZeroColorMaskBug)
// Use this to select which configurations (e.g. which renderer, which GLES major version) these
// tests should be run against. D3D11 Feature Level 9_3 uses different D3D formats for vertex
// attribs compared to Feature Levels 10_0+, so we should test them separately.
ANGLE_INSTANTIATE_TEST_ES2_AND_ES3(ColorMaskTest);
ANGLE_INSTANTIATE_TEST_ES2_AND_ES3_AND(ColorMaskTest, WithDirectSPIRVGeneration(ES2_VULKAN()));
} // namespace angle
......@@ -515,7 +515,7 @@ class GLSLTest_ES3 : public GLSLTest
class GLSLTest_ES31 : public GLSLTest
{};
std::string BuillBigInitialStackShader(int length)
std::string BuildBigInitialStackShader(int length)
{
std::string result;
result += "void main() { \n";
......@@ -1482,6 +1482,25 @@ TEST_P(GLSLTest_ES3, MissingReturnStructOfArrays)
EXPECT_NE(0u, program);
}
// Verify that non-const index used on an array returned by a function compiles
TEST_P(GLSLTest_ES3, ReturnArrayOfStructsThenNonConstIndex)
{
constexpr char kVS[] = R"(#version 300 es
in float v_varying;
struct s { float a; int b; vec2 c; };
s[2] f()
{
return s[2](s(v_varying, 1, vec2(1.0, 1.0)), s(v_varying / 2.0, 1, vec2(1.0, 1.0)));
}
void main()
{
gl_Position = vec4(f()[uint(v_varying)].a, 0, 0, 1);
})";
GLuint program = CompileProgram(kVS, essl3_shaders::fs::Red());
EXPECT_NE(0u, program);
}
// Verify that using invariant(all) in both shaders fails in ESSL 3.00.
TEST_P(GLSLTest_ES3, InvariantAllBoth)
{
......@@ -8178,7 +8197,7 @@ TEST_P(GLSLTest, MemoryExhaustedTest)
{
ANGLE_SKIP_TEST_IF(IsD3D11_FL93());
GLuint program =
CompileProgram(essl1_shaders::vs::Simple(), BuillBigInitialStackShader(36).c_str());
CompileProgram(essl1_shaders::vs::Simple(), BuildBigInitialStackShader(36).c_str());
EXPECT_NE(0u, program);
}
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment