blob: ca6cdb7b9ad933ddae736a9c4f6bfcc48be28c8d [file] [log] [blame]
//
// Copyright 2016 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.
//
// TranslatorSPIRV:
// A set of transformations that prepare the AST to be compatible with GL_KHR_vulkan_glsl followed
// by a pass that generates SPIR-V.
// See: https://www.khronos.org/registry/vulkan/specs/misc/GL_KHR_vulkan_glsl.txt
//
#include "compiler/translator/spirv/TranslatorSPIRV.h"
#include "angle_gl.h"
#include "common/PackedEnums.h"
#include "common/utilities.h"
#include "compiler/translator/ImmutableStringBuilder.h"
#include "compiler/translator/IntermNode.h"
#include "compiler/translator/StaticType.h"
#include "compiler/translator/spirv/BuiltinsWorkaround.h"
#include "compiler/translator/spirv/OutputSPIRV.h"
#include "compiler/translator/tree_ops/DeclarePerVertexBlocks.h"
#include "compiler/translator/tree_ops/MonomorphizeUnsupportedFunctions.h"
#include "compiler/translator/tree_ops/RecordConstantPrecision.h"
#include "compiler/translator/tree_ops/RemoveAtomicCounterBuiltins.h"
#include "compiler/translator/tree_ops/RemoveInactiveInterfaceVariables.h"
#include "compiler/translator/tree_ops/RewriteArrayOfArrayOfOpaqueUniforms.h"
#include "compiler/translator/tree_ops/RewriteAtomicCounters.h"
#include "compiler/translator/tree_ops/RewriteDfdy.h"
#include "compiler/translator/tree_ops/RewriteStructSamplers.h"
#include "compiler/translator/tree_ops/SeparateStructFromUniformDeclarations.h"
#include "compiler/translator/tree_ops/spirv/ClampGLLayer.h"
#include "compiler/translator/tree_ops/spirv/EmulateAdvancedBlendEquations.h"
#include "compiler/translator/tree_ops/spirv/EmulateDithering.h"
#include "compiler/translator/tree_ops/spirv/EmulateFragColorData.h"
#include "compiler/translator/tree_ops/spirv/EmulateFramebufferFetch.h"
#include "compiler/translator/tree_ops/spirv/EmulateYUVBuiltIns.h"
#include "compiler/translator/tree_ops/spirv/FlagSamplersWithTexelFetch.h"
#include "compiler/translator/tree_ops/spirv/ReswizzleYUVOps.h"
#include "compiler/translator/tree_ops/spirv/RewriteInterpolateAtOffset.h"
#include "compiler/translator/tree_ops/spirv/RewriteR32fImages.h"
#include "compiler/translator/tree_util/BuiltIn.h"
#include "compiler/translator/tree_util/DriverUniform.h"
#include "compiler/translator/tree_util/FindFunction.h"
#include "compiler/translator/tree_util/FindMain.h"
#include "compiler/translator/tree_util/FindSymbolNode.h"
#include "compiler/translator/tree_util/IntermNode_util.h"
#include "compiler/translator/tree_util/ReplaceClipCullDistanceVariable.h"
#include "compiler/translator/tree_util/ReplaceVariable.h"
#include "compiler/translator/tree_util/RewriteSampleMaskVariable.h"
#include "compiler/translator/tree_util/RunAtTheBeginningOfShader.h"
#include "compiler/translator/tree_util/RunAtTheEndOfShader.h"
#include "compiler/translator/tree_util/SpecializationConstant.h"
#include "compiler/translator/util.h"
namespace sh
{
namespace
{
constexpr ImmutableString kFlippedPointCoordName = ImmutableString("flippedPointCoord");
constexpr ImmutableString kFlippedFragCoordName = ImmutableString("flippedFragCoord");
constexpr ImmutableString kDefaultUniformsBlockName = ImmutableString("defaultUniforms");
bool IsDefaultUniform(const TType &type)
{
return type.getQualifier() == EvqUniform && type.getInterfaceBlock() == nullptr &&
!IsOpaqueType(type.getBasicType());
}
class ReplaceDefaultUniformsTraverser : public TIntermTraverser
{
public:
ReplaceDefaultUniformsTraverser(const VariableReplacementMap &variableMap)
: TIntermTraverser(true, false, false), mVariableMap(variableMap)
{}
bool visitDeclaration(Visit visit, TIntermDeclaration *node) override
{
const TIntermSequence &sequence = *(node->getSequence());
TIntermTyped *variable = sequence.front()->getAsTyped();
const TType &type = variable->getType();
if (IsDefaultUniform(type))
{
// Remove the uniform declaration.
TIntermSequence emptyReplacement;
mMultiReplacements.emplace_back(getParentNode()->getAsBlock(), node,
std::move(emptyReplacement));
return false;
}
return true;
}
void visitSymbol(TIntermSymbol *symbol) override
{
const TVariable &variable = symbol->variable();
const TType &type = variable.getType();
if (!IsDefaultUniform(type) || gl::IsBuiltInName(variable.name().data()))
{
return;
}
ASSERT(mVariableMap.count(&variable) > 0);
queueReplacement(mVariableMap.at(&variable)->deepCopy(), OriginalNode::IS_DROPPED);
}
private:
const VariableReplacementMap &mVariableMap;
};
bool DeclareDefaultUniforms(TranslatorSPIRV *compiler,
TIntermBlock *root,
TSymbolTable *symbolTable,
gl::ShaderType shaderType)
{
// First, collect all default uniforms and declare a uniform block.
TFieldList *uniformList = new TFieldList;
TVector<const TVariable *> uniformVars;
for (TIntermNode *node : *root->getSequence())
{
TIntermDeclaration *decl = node->getAsDeclarationNode();
if (decl == nullptr)
{
continue;
}
const TIntermSequence &sequence = *(decl->getSequence());
TIntermSymbol *symbol = sequence.front()->getAsSymbolNode();
if (symbol == nullptr)
{
continue;
}
const TType &type = symbol->getType();
if (IsDefaultUniform(type))
{
TType *fieldType = new TType(type);
uniformList->push_back(new TField(fieldType, symbol->getName(), symbol->getLine(),
symbol->variable().symbolType()));
uniformVars.push_back(&symbol->variable());
}
}
TLayoutQualifier layoutQualifier = TLayoutQualifier::Create();
layoutQualifier.blockStorage = EbsStd140;
const TVariable *uniformBlock = DeclareInterfaceBlock(
root, symbolTable, uniformList, EvqUniform, layoutQualifier, TMemoryQualifier::Create(), 0,
kDefaultUniformsBlockName, ImmutableString(""));
compiler->assignSpirvId(uniformBlock->getType().getInterfaceBlock()->uniqueId(),
vk::spirv::kIdDefaultUniformsBlock);
// Create a map from the uniform variables to new variables that reference the fields of the
// block.
VariableReplacementMap variableMap;
for (size_t fieldIndex = 0; fieldIndex < uniformVars.size(); ++fieldIndex)
{
const TVariable *variable = uniformVars[fieldIndex];
TType *replacementType = new TType(variable->getType());
replacementType->setInterfaceBlockField(uniformBlock->getType().getInterfaceBlock(),
fieldIndex);
TVariable *replacementVariable =
new TVariable(symbolTable, variable->name(), replacementType, variable->symbolType());
variableMap[variable] = new TIntermSymbol(replacementVariable);
}
// Finally transform the AST and make sure references to the uniforms are replaced with the new
// variables.
ReplaceDefaultUniformsTraverser defaultTraverser(variableMap);
root->traverse(&defaultTraverser);
return defaultTraverser.updateTree(compiler, root);
}
// Replaces a builtin variable with a version that is rotated and corrects the X and Y coordinates.
[[nodiscard]] bool RotateAndFlipBuiltinVariable(TCompiler *compiler,
TIntermBlock *root,
TIntermSequence *insertSequence,
TIntermTyped *swapXY,
TIntermTyped *flipXY,
TSymbolTable *symbolTable,
const TVariable *builtin,
const ImmutableString &flippedVariableName,
TIntermTyped *pivot)
{
// Create a symbol reference to 'builtin'.
TIntermSymbol *builtinRef = new TIntermSymbol(builtin);
// Create a symbol reference to our new variable that will hold the modified builtin.
TType *type = new TType(builtin->getType());
type->setQualifier(EvqGlobal);
type->setPrimarySize(builtin->getType().getNominalSize());
TVariable *replacementVar =
new TVariable(symbolTable, flippedVariableName, type, SymbolType::AngleInternal);
DeclareGlobalVariable(root, replacementVar);
TIntermSymbol *flippedBuiltinRef = new TIntermSymbol(replacementVar);
// Use this new variable instead of 'builtin' everywhere.
if (!ReplaceVariable(compiler, root, builtin, replacementVar))
{
return false;
}
// Create the expression "(swapXY ? builtin.yx : builtin.xy)"
TIntermTyped *builtinXY = new TIntermSwizzle(builtinRef, {0, 1});
TIntermTyped *builtinYX = new TIntermSwizzle(builtinRef->deepCopy(), {1, 0});
builtinXY = new TIntermTernary(swapXY, builtinYX, builtinXY);
// Create the expression "(builtin.xy - pivot) * flipXY + pivot
TIntermBinary *removePivot = new TIntermBinary(EOpSub, builtinXY, pivot);
TIntermBinary *inverseXY = new TIntermBinary(EOpMul, removePivot, flipXY);
TIntermBinary *plusPivot = new TIntermBinary(EOpAdd, inverseXY, pivot->deepCopy());
// Create the corrected variable and copy the value of the original builtin.
TIntermBinary *assignment =
new TIntermBinary(EOpAssign, flippedBuiltinRef, builtinRef->deepCopy());
// Create an assignment to the replaced variable's .xy.
TIntermSwizzle *correctedXY = new TIntermSwizzle(flippedBuiltinRef->deepCopy(), {0, 1});
TIntermBinary *assignToXY = new TIntermBinary(EOpAssign, correctedXY, plusPivot);
// Add this assigment at the beginning of the main function
insertSequence->insert(insertSequence->begin(), assignToXY);
insertSequence->insert(insertSequence->begin(), assignment);
return compiler->validateAST(root);
}
TIntermSequence *GetMainSequence(TIntermBlock *root)
{
TIntermFunctionDefinition *main = FindMain(root);
return main->getBody()->getSequence();
}
// Declares a new variable to replace gl_DepthRange, its values are fed from a driver uniform.
[[nodiscard]] bool ReplaceGLDepthRangeWithDriverUniform(TCompiler *compiler,
TIntermBlock *root,
const DriverUniform *driverUniforms,
TSymbolTable *symbolTable)
{
// Create a symbol reference to "gl_DepthRange"
const TVariable *depthRangeVar = static_cast<const TVariable *>(
symbolTable->findBuiltIn(ImmutableString("gl_DepthRange"), 0));
// ANGLEUniforms.depthRange
TIntermTyped *angleEmulatedDepthRangeRef = driverUniforms->getDepthRange();
// Use this variable instead of gl_DepthRange everywhere.
return ReplaceVariableWithTyped(compiler, root, depthRangeVar, angleEmulatedDepthRangeRef);
}
// Declares a new variable to replace gl_BoundingBoxEXT, its values are fed from a global temporary
// variable.
[[nodiscard]] bool ReplaceGLBoundingBoxWithGlobal(TCompiler *compiler,
TIntermBlock *root,
TSymbolTable *symbolTable,
int shaderVersion)
{
// Declare the replacement bounding box variable type
TType *emulatedBoundingBoxDeclType = new TType(EbtFloat, EbpHigh, EvqGlobal, 4);
emulatedBoundingBoxDeclType->makeArray(2u);
TVariable *ANGLEBoundingBoxVar = new TVariable(
symbolTable->nextUniqueId(), ImmutableString("ANGLEBoundingBox"), SymbolType::AngleInternal,
TExtension::EXT_primitive_bounding_box, emulatedBoundingBoxDeclType);
DeclareGlobalVariable(root, ANGLEBoundingBoxVar);
const TVariable *builtinBoundingBoxVar;
bool replacementResult = true;
// Create a symbol reference to "gl_BoundingBoxEXT"
builtinBoundingBoxVar = static_cast<const TVariable *>(
symbolTable->findBuiltIn(ImmutableString("gl_BoundingBoxEXT"), shaderVersion));
if (builtinBoundingBoxVar != nullptr)
{
// Use the replacement variable instead of builtin gl_BoundingBoxEXT everywhere.
replacementResult &=
ReplaceVariable(compiler, root, builtinBoundingBoxVar, ANGLEBoundingBoxVar);
}
// Create a symbol reference to "gl_BoundingBoxOES"
builtinBoundingBoxVar = static_cast<const TVariable *>(
symbolTable->findBuiltIn(ImmutableString("gl_BoundingBoxOES"), shaderVersion));
if (builtinBoundingBoxVar != nullptr)
{
// Use the replacement variable instead of builtin gl_BoundingBoxOES everywhere.
replacementResult &=
ReplaceVariable(compiler, root, builtinBoundingBoxVar, ANGLEBoundingBoxVar);
}
if (shaderVersion >= 320)
{
// Create a symbol reference to "gl_BoundingBox"
builtinBoundingBoxVar = static_cast<const TVariable *>(
symbolTable->findBuiltIn(ImmutableString("gl_BoundingBox"), shaderVersion));
if (builtinBoundingBoxVar != nullptr)
{
// Use the replacement variable instead of builtin gl_BoundingBox everywhere.
replacementResult &=
ReplaceVariable(compiler, root, builtinBoundingBoxVar, ANGLEBoundingBoxVar);
}
}
return replacementResult;
}
[[nodiscard]] bool AddXfbEmulationSupport(TranslatorSPIRV *compiler,
TIntermBlock *root,
TSymbolTable *symbolTable,
const DriverUniform *driverUniforms)
{
// Generate the following function and place it before main(). This function takes a "strides"
// parameter that is determined at link time, and calculates for each transform feedback buffer
// (of which there are a maximum of four) what the starting index is to write to the output
// buffer.
//
// ivec4 ANGLEGetXfbOffsets(ivec4 strides)
// {
// int xfbIndex = gl_VertexIndex
// + gl_InstanceIndex * ANGLEUniforms.xfbVerticesPerInstance;
// return ANGLEUniforms.xfbBufferOffsets + xfbIndex * strides;
// }
constexpr uint32_t kMaxXfbBuffers = 4;
const TType *ivec4Type = StaticType::GetBasic<EbtInt, EbpHigh, kMaxXfbBuffers>();
TType *stridesType = new TType(*ivec4Type);
stridesType->setQualifier(EvqParamConst);
// Create the parameter variable.
TVariable *stridesVar = new TVariable(symbolTable, ImmutableString("strides"), stridesType,
SymbolType::AngleInternal);
TIntermSymbol *stridesSymbol = new TIntermSymbol(stridesVar);
// Create references to gl_VertexIndex, gl_InstanceIndex, ANGLEUniforms.xfbVerticesPerInstance
// and ANGLEUniforms.xfbBufferOffsets.
TIntermSymbol *vertexIndex = new TIntermSymbol(BuiltInVariable::gl_VertexIndex());
TIntermSymbol *instanceIndex = new TIntermSymbol(BuiltInVariable::gl_InstanceIndex());
TIntermTyped *xfbVerticesPerInstance = driverUniforms->getXfbVerticesPerInstance();
TIntermTyped *xfbBufferOffsets = driverUniforms->getXfbBufferOffsets();
// gl_InstanceIndex * ANGLEUniforms.xfbVerticesPerInstance
TIntermBinary *xfbInstanceIndex =
new TIntermBinary(EOpMul, instanceIndex, xfbVerticesPerInstance);
// gl_VertexIndex + |xfbInstanceIndex|
TIntermBinary *xfbIndex = new TIntermBinary(EOpAdd, vertexIndex, xfbInstanceIndex);
// |xfbIndex| * |strides|
TIntermBinary *xfbStrides = new TIntermBinary(EOpVectorTimesScalar, xfbIndex, stridesSymbol);
// ANGLEUniforms.xfbBufferOffsets + |xfbStrides|
TIntermBinary *xfbOffsets = new TIntermBinary(EOpAdd, xfbBufferOffsets, xfbStrides);
// Create the function body, which has a single return statement. Note that the `xfbIndex`
// variable declared in the comment at the beginning of this function is simply replaced in the
// return statement for brevity.
TIntermBlock *body = new TIntermBlock;
body->appendStatement(new TIntermBranch(EOpReturn, xfbOffsets));
// Declare the function
TFunction *getOffsetsFunction =
new TFunction(symbolTable, ImmutableString("ANGLEGetXfbOffsets"), SymbolType::AngleInternal,
ivec4Type, true);
getOffsetsFunction->addParameter(stridesVar);
compiler->assignSpirvId(getOffsetsFunction->uniqueId(),
vk::spirv::kIdXfbEmulationGetOffsetsFunction);
TIntermFunctionDefinition *functionDef =
CreateInternalFunctionDefinitionNode(*getOffsetsFunction, body);
// Insert the function declaration before main().
const size_t mainIndex = FindMainIndex(root);
root->insertChildNodes(mainIndex, {functionDef});
// Generate the following function and place it before main(). This function will be filled
// with transform feedback capture code at link time.
//
// void ANGLECaptureXfb()
// {
// }
const TType *voidType = StaticType::GetBasic<EbtVoid, EbpUndefined>();
// Create the function body, which is empty.
body = new TIntermBlock;
// Declare the function
TFunction *xfbCaptureFunction = new TFunction(symbolTable, ImmutableString("ANGLECaptureXfb"),
SymbolType::AngleInternal, voidType, false);
compiler->assignSpirvId(xfbCaptureFunction->uniqueId(),
vk::spirv::kIdXfbEmulationCaptureFunction);
// Insert the function declaration before main().
root->insertChildNodes(mainIndex,
{CreateInternalFunctionDefinitionNode(*xfbCaptureFunction, body)});
// Create the following logic and add it at the end of main():
//
// ANGLECaptureXfb();
//
// Create the function call
TIntermAggregate *captureXfbCall =
TIntermAggregate::CreateFunctionCall(*xfbCaptureFunction, {});
// Run it at the end of the shader.
if (!RunAtTheEndOfShader(compiler, root, captureXfbCall, symbolTable))
{
return false;
}
// Additionally, generate the following storage buffer declarations used to capture transform
// feedback output. Again, there's a maximum of four buffers.
//
// buffer ANGLEXfbBuffer0
// {
// float xfbOut[];
// } ANGLEXfb0;
// buffer ANGLEXfbBuffer1
// {
// float xfbOut[];
// } ANGLEXfb1;
// ...
for (uint32_t bufferIndex = 0; bufferIndex < kMaxXfbBuffers; ++bufferIndex)
{
TFieldList *fieldList = new TFieldList;
TType *xfbOutType = new TType(EbtFloat, EbpHigh, EvqGlobal);
xfbOutType->makeArray(0);
TField *field = new TField(xfbOutType, ImmutableString("xfbOut"), TSourceLoc(),
SymbolType::AngleInternal);
fieldList->push_back(field);
static_assert(
kMaxXfbBuffers < 10,
"ImmutableStringBuilder memory size below needs to accomodate the number of buffers");
ImmutableString blockName = BuildConcatenatedImmutableString("ANGLEXfbBuffer", bufferIndex);
ImmutableString varName = BuildConcatenatedImmutableString("ANGLEXfb", bufferIndex);
TLayoutQualifier layoutQualifier = TLayoutQualifier::Create();
layoutQualifier.blockStorage = EbsStd430;
const TVariable *xfbBuffer =
DeclareInterfaceBlock(root, symbolTable, fieldList, EvqBuffer, layoutQualifier,
TMemoryQualifier::Create(), 0, blockName, varName);
static_assert(vk::spirv::kIdXfbEmulationBufferBlockOne ==
vk::spirv::kIdXfbEmulationBufferBlockZero + 1);
static_assert(vk::spirv::kIdXfbEmulationBufferBlockTwo ==
vk::spirv::kIdXfbEmulationBufferBlockZero + 2);
static_assert(vk::spirv::kIdXfbEmulationBufferBlockThree ==
vk::spirv::kIdXfbEmulationBufferBlockZero + 3);
static_assert(vk::spirv::kIdXfbEmulationBufferVarOne ==
vk::spirv::kIdXfbEmulationBufferVarZero + 1);
static_assert(vk::spirv::kIdXfbEmulationBufferVarTwo ==
vk::spirv::kIdXfbEmulationBufferVarZero + 2);
static_assert(vk::spirv::kIdXfbEmulationBufferVarThree ==
vk::spirv::kIdXfbEmulationBufferVarZero + 3);
compiler->assignSpirvId(xfbBuffer->getType().getInterfaceBlock()->uniqueId(),
vk::spirv::kIdXfbEmulationBufferBlockZero + bufferIndex);
compiler->assignSpirvId(xfbBuffer->uniqueId(),
vk::spirv::kIdXfbEmulationBufferVarZero + bufferIndex);
}
return compiler->validateAST(root);
}
[[nodiscard]] bool AddXfbExtensionSupport(TranslatorSPIRV *compiler,
TIntermBlock *root,
TSymbolTable *symbolTable,
const DriverUniform *driverUniforms)
{
// Generate the following output varying declaration used to capture transform feedback output
// from gl_Position, as it can't be captured directly due to changes that are applied to it for
// clip-space correction and pre-rotation.
//
// out vec4 ANGLEXfbPosition;
const TType *vec4Type = nullptr;
switch (compiler->getShaderType())
{
case GL_VERTEX_SHADER:
vec4Type = StaticType::Get<EbtFloat, EbpHigh, EvqVertexOut, 4, 1>();
break;
case GL_TESS_EVALUATION_SHADER_EXT:
vec4Type = StaticType::Get<EbtFloat, EbpHigh, EvqTessEvaluationOut, 4, 1>();
break;
case GL_GEOMETRY_SHADER_EXT:
vec4Type = StaticType::Get<EbtFloat, EbpHigh, EvqGeometryOut, 4, 1>();
break;
default:
UNREACHABLE();
}
TVariable *varyingVar = new TVariable(symbolTable, ImmutableString("ANGLEXfbPosition"),
vec4Type, SymbolType::AngleInternal);
compiler->assignSpirvId(varyingVar->uniqueId(), vk::spirv::kIdXfbExtensionPosition);
TIntermDeclaration *varyingDecl = new TIntermDeclaration();
varyingDecl->appendDeclarator(new TIntermSymbol(varyingVar));
// Insert the varying declaration before the first function.
const size_t firstFunctionIndex = FindFirstFunctionDefinitionIndex(root);
root->insertChildNodes(firstFunctionIndex, {varyingDecl});
return compiler->validateAST(root);
}
[[nodiscard]] bool AddVertexTransformationSupport(TranslatorSPIRV *compiler,
const ShCompileOptions &compileOptions,
TIntermBlock *root,
TSymbolTable *symbolTable,
SpecConst *specConst,
const DriverUniform *driverUniforms)
{
// In GL the viewport transformation is slightly different - see the GL 2.0 spec section "2.12.1
// Controlling the Viewport". In Vulkan the corresponding spec section is currently "23.4.
// Coordinate Transformations". The following transformation needs to be done:
//
// z_vk = 0.5 * (w_gl + z_gl)
//
// where z_vk is the depth output of a Vulkan geometry-stage shader and z_gl is the same for GL.
//
// Generate the following function and place it before main(). This function takes
// gl_Position and rotates xy, and adjusts z (if necessary).
//
// vec4 ANGLETransformPosition(vec4 position)
// {
// return vec4((swapXY ? position.yx : position.xy) * flipXY,
// transformDepth ? (gl_Position.z + gl_Position.w) / 2 : gl_Position.z,
// gl_Postion.w);
// }
const TType *vec4Type = StaticType::GetBasic<EbtFloat, EbpHigh, 4>();
TType *positionType = new TType(*vec4Type);
positionType->setQualifier(EvqParamConst);
// Create the parameter variable.
TVariable *positionVar = new TVariable(symbolTable, ImmutableString("position"), positionType,
SymbolType::AngleInternal);
TIntermSymbol *positionSymbol = new TIntermSymbol(positionVar);
// swapXY ? position.yx : position.xy
TIntermTyped *swapXY = specConst->getSwapXY();
if (swapXY == nullptr)
{
swapXY = driverUniforms->getSwapXY();
}
TIntermTyped *xy = new TIntermSwizzle(positionSymbol, {0, 1});
TIntermTyped *swappedXY = new TIntermSwizzle(positionSymbol->deepCopy(), {1, 0});
TIntermTyped *rotatedXY = new TIntermTernary(swapXY, swappedXY, xy);
// (swapXY ? position.yx : position.xy) * flipXY
TIntermTyped *flipXY = driverUniforms->getFlipXY(symbolTable, DriverUniformFlip::PreFragment);
TIntermTyped *rotatedFlippedXY = new TIntermBinary(EOpMul, rotatedXY, flipXY);
// (gl_Position.z + gl_Position.w) / 2
TIntermTyped *z = new TIntermSwizzle(positionSymbol->deepCopy(), {2});
TIntermTyped *w = new TIntermSwizzle(positionSymbol->deepCopy(), {3});
TIntermTyped *transformedDepth = z;
if (compileOptions.addVulkanDepthCorrection)
{
TIntermBinary *zPlusW = new TIntermBinary(EOpAdd, z, w->deepCopy());
TIntermBinary *halfZPlusW =
new TIntermBinary(EOpMul, zPlusW, CreateFloatNode(0.5, EbpMedium));
// transformDepth ? (gl_Position.z + gl_Position.w) / 2 : gl_Position.z,
TIntermTyped *transformDepth = driverUniforms->getTransformDepth();
transformedDepth = new TIntermTernary(transformDepth, halfZPlusW, z->deepCopy());
}
// vec4(...);
TIntermSequence args = {
rotatedFlippedXY,
transformedDepth,
w,
};
TIntermTyped *transformedPosition = TIntermAggregate::CreateConstructor(*vec4Type, &args);
// Create the function body, which has a single return statement.
TIntermBlock *body = new TIntermBlock;
body->appendStatement(new TIntermBranch(EOpReturn, transformedPosition));
// Declare the function
TFunction *transformPositionFunction =
new TFunction(symbolTable, ImmutableString("ANGLETransformPosition"),
SymbolType::AngleInternal, vec4Type, true);
transformPositionFunction->addParameter(positionVar);
compiler->assignSpirvId(transformPositionFunction->uniqueId(),
vk::spirv::kIdTransformPositionFunction);
TIntermFunctionDefinition *functionDef =
CreateInternalFunctionDefinitionNode(*transformPositionFunction, body);
// Insert the function declaration before main().
const size_t mainIndex = FindMainIndex(root);
root->insertChildNodes(mainIndex, {functionDef});
return compiler->validateAST(root);
}
[[nodiscard]] bool InsertFragCoordCorrection(TCompiler *compiler,
const ShCompileOptions &compileOptions,
TIntermBlock *root,
TIntermSequence *insertSequence,
TSymbolTable *symbolTable,
SpecConst *specConst,
const DriverUniform *driverUniforms)
{
TIntermTyped *flipXY = driverUniforms->getFlipXY(symbolTable, DriverUniformFlip::Fragment);
TIntermTyped *pivot = driverUniforms->getHalfRenderArea();
TIntermTyped *swapXY = specConst->getSwapXY();
if (swapXY == nullptr)
{
swapXY = driverUniforms->getSwapXY();
}
const TVariable *fragCoord = static_cast<const TVariable *>(
symbolTable->findBuiltIn(ImmutableString("gl_FragCoord"), compiler->getShaderVersion()));
return RotateAndFlipBuiltinVariable(compiler, root, insertSequence, swapXY, flipXY, symbolTable,
fragCoord, kFlippedFragCoordName, pivot);
}
bool HasFramebufferFetch(const TExtensionBehavior &extBehavior,
const ShCompileOptions &compileOptions)
{
return IsExtensionEnabled(extBehavior, TExtension::EXT_shader_framebuffer_fetch) ||
IsExtensionEnabled(extBehavior, TExtension::EXT_shader_framebuffer_fetch_non_coherent) ||
IsExtensionEnabled(extBehavior, TExtension::ARM_shader_framebuffer_fetch) ||
IsExtensionEnabled(extBehavior,
TExtension::ARM_shader_framebuffer_fetch_depth_stencil) ||
IsExtensionEnabled(extBehavior, TExtension::NV_shader_framebuffer_fetch) ||
(compileOptions.pls.type == ShPixelLocalStorageType::FramebufferFetch &&
IsExtensionEnabled(extBehavior, TExtension::ANGLE_shader_pixel_local_storage));
}
template <typename Variable>
Variable *FindShaderVariable(std::vector<Variable> *vars, const ImmutableString &name)
{
for (Variable &var : *vars)
{
if (name == var.name)
{
return &var;
}
}
UNREACHABLE();
return nullptr;
}
ShaderVariable *FindIOBlockShaderVariable(std::vector<ShaderVariable> *vars,
const ImmutableString &name)
{
for (ShaderVariable &var : *vars)
{
if (name == var.structOrBlockName)
{
return &var;
}
}
UNREACHABLE();
return nullptr;
}
ShaderVariable *FindUniformFieldShaderVariable(std::vector<ShaderVariable> *vars,
const ImmutableString &name,
const char *prefix)
{
for (ShaderVariable &var : *vars)
{
// The name of the sampler is derived from the uniform name + fields
// that reach the uniform, concatenated with '_' per RewriteStructSamplers.
std::string varName = prefix;
varName += '_';
varName += var.name;
if (name == varName)
{
return &var;
}
ShaderVariable *field = FindUniformFieldShaderVariable(&var.fields, name, varName.c_str());
if (field != nullptr)
{
return field;
}
}
return nullptr;
}
ShaderVariable *FindUniformShaderVariable(std::vector<ShaderVariable> *vars,
const ImmutableString &name)
{
for (ShaderVariable &var : *vars)
{
if (name == var.name)
{
return &var;
}
// Note: samplers in structs are moved out. Such samplers will be found in the fields of
// the struct uniform.
ShaderVariable *field = FindUniformFieldShaderVariable(&var.fields, name, var.name.c_str());
if (field != nullptr)
{
return field;
}
}
UNREACHABLE();
return nullptr;
}
void SetSpirvIdInFields(uint32_t id, std::vector<ShaderVariable> *fields)
{
for (ShaderVariable &field : *fields)
{
field.id = id;
SetSpirvIdInFields(id, &field.fields);
}
}
} // anonymous namespace
TranslatorSPIRV::TranslatorSPIRV(sh::GLenum type, ShShaderSpec spec)
: TCompiler(type, spec, SH_SPIRV_VULKAN_OUTPUT), mFirstUnusedSpirvId(0)
{}
bool TranslatorSPIRV::translateImpl(TIntermBlock *root,
const ShCompileOptions &compileOptions,
PerformanceDiagnostics * /*perfDiagnostics*/,
SpecConst *specConst,
DriverUniform *driverUniforms)
{
if (getShaderType() == GL_VERTEX_SHADER)
{
if (!ShaderBuiltinsWorkaround(this, root, &getSymbolTable(), compileOptions))
{
return false;
}
}
// Write out default uniforms into a uniform block assigned to a specific set/binding.
int defaultUniformCount = 0;
int aggregateTypesUsedForUniforms = 0;
int r32fImageCount = 0;
int atomicCounterCount = 0;
for (const auto &uniform : getUniforms())
{
if (!uniform.isBuiltIn() && uniform.active && !gl::IsOpaqueType(uniform.type))
{
++defaultUniformCount;
}
if (uniform.isStruct() || uniform.isArrayOfArrays())
{
++aggregateTypesUsedForUniforms;
}
if (uniform.active && gl::IsImageType(uniform.type) && uniform.imageUnitFormat == GL_R32F)
{
++r32fImageCount;
}
if (uniform.active && gl::IsAtomicCounterType(uniform.type))
{
++atomicCounterCount;
}
}
// Remove declarations of inactive shader interface variables so SPIR-V transformer doesn't need
// to replace them. Note that currently, CollectVariables marks every field of an active
// uniform that's of struct type as active, i.e. no extracted sampler is inactive, so this can
// be done before extracting samplers from structs.
if (!RemoveInactiveInterfaceVariables(this, root, &getSymbolTable(), getAttributes(),
getInputVaryings(), getOutputVariables(), getUniforms(),
getInterfaceBlocks(), true))
{
return false;
}
// If there are any function calls that take array-of-array of opaque uniform parameters, or
// other opaque uniforms that need special handling in Vulkan, such as atomic counters,
// monomorphize the functions by removing said parameters and replacing them in the function
// body with the call arguments.
//
// This has a few benefits:
//
// - It dramatically simplifies future transformations w.r.t to samplers in structs, array of
// arrays of opaque types, atomic counters etc.
// - Avoids the need for shader*ArrayDynamicIndexing Vulkan features.
UnsupportedFunctionArgsBitSet args{UnsupportedFunctionArgs::StructContainingSamplers,
UnsupportedFunctionArgs::ArrayOfArrayOfSamplerOrImage,
UnsupportedFunctionArgs::AtomicCounter,
UnsupportedFunctionArgs::Image};
if (!MonomorphizeUnsupportedFunctions(this, root, &getSymbolTable(), args))
{
return false;
}
if (aggregateTypesUsedForUniforms > 0)
{
if (!SeparateStructFromUniformDeclarations(this, root, &getSymbolTable()))
{
return false;
}
int removedUniformsCount;
if (!RewriteStructSamplers(this, root, &getSymbolTable(), &removedUniformsCount))
{
return false;
}
defaultUniformCount -= removedUniformsCount;
}
// Replace array of array of opaque uniforms with a flattened array. This is run after
// MonomorphizeUnsupportedFunctions and RewriteStructSamplers so that it's not possible for an
// array of array of opaque type to be partially subscripted and passed to a function.
if (!RewriteArrayOfArrayOfOpaqueUniforms(this, root, &getSymbolTable()))
{
return false;
}
if (!FlagSamplersForTexelFetch(this, root, &getSymbolTable(), &mUniforms))
{
return false;
}
gl::ShaderType packedShaderType = gl::FromGLenum<gl::ShaderType>(getShaderType());
if (defaultUniformCount > 0)
{
if (!DeclareDefaultUniforms(this, root, &getSymbolTable(), packedShaderType))
{
return false;
}
}
if (getShaderType() == GL_COMPUTE_SHADER)
{
driverUniforms->addComputeDriverUniformsToShader(root, &getSymbolTable());
}
else
{
driverUniforms->addGraphicsDriverUniformsToShader(root, &getSymbolTable());
}
assignSpirvId(
driverUniforms->getDriverUniformsVariable()->getType().getInterfaceBlock()->uniqueId(),
vk::spirv::kIdDriverUniformsBlock);
if (r32fImageCount > 0 && compileOptions.emulateR32fImageAtomicExchange)
{
if (!RewriteR32fImages(this, root, &getSymbolTable()))
{
return false;
}
}
if (atomicCounterCount > 0)
{
// ANGLEUniforms.acbBufferOffsets
const TIntermTyped *acbBufferOffsets = driverUniforms->getAcbBufferOffsets();
const TVariable *atomicCounters = nullptr;
if (!RewriteAtomicCounters(this, root, &getSymbolTable(), acbBufferOffsets,
&atomicCounters))
{
return false;
}
assignSpirvId(atomicCounters->getType().getInterfaceBlock()->uniqueId(),
vk::spirv::kIdAtomicCounterBlock);
}
else if (getShaderVersion() >= 310)
{
// Vulkan doesn't support Atomic Storage as a Storage Class, but we've seen
// cases where builtins are using it even with no active atomic counters.
// This pass simply removes those builtins in that scenario.
if (!RemoveAtomicCounterBuiltins(this, root))
{
return false;
}
}
if (packedShaderType != gl::ShaderType::Compute)
{
if (!ReplaceGLDepthRangeWithDriverUniform(this, root, driverUniforms, &getSymbolTable()))
{
return false;
}
// Search for the gl_ClipDistance/gl_CullDistance usage, if its used, we need to do some
// replacements.
bool useClipDistance = false;
bool useCullDistance = false;
for (const ShaderVariable &outputVarying : mOutputVaryings)
{
if (outputVarying.name == "gl_ClipDistance")
{
useClipDistance = true;
}
else if (outputVarying.name == "gl_CullDistance")
{
useCullDistance = true;
}
}
for (const ShaderVariable &inputVarying : mInputVaryings)
{
if (inputVarying.name == "gl_ClipDistance")
{
useClipDistance = true;
}
else if (inputVarying.name == "gl_CullDistance")
{
useCullDistance = true;
}
}
if (useClipDistance &&
!ReplaceClipDistanceAssignments(this, root, &getSymbolTable(), getShaderType(),
driverUniforms->getClipDistancesEnabled()))
{
return false;
}
if (useCullDistance &&
!ReplaceCullDistanceAssignments(this, root, &getSymbolTable(), getShaderType()))
{
return false;
}
}
if (gl::ShaderTypeSupportsTransformFeedback(packedShaderType))
{
if (compileOptions.addVulkanXfbExtensionSupportCode)
{
// Add support code for transform feedback extension.
if (!AddXfbExtensionSupport(this, root, &getSymbolTable(), driverUniforms))
{
return false;
}
}
// Add support code for pre-rotation and depth correction in the vertex processing stages.
if (!AddVertexTransformationSupport(this, compileOptions, root, &getSymbolTable(),
specConst, driverUniforms))
{
return false;
}
}
if (IsExtensionEnabled(getExtensionBehavior(), TExtension::EXT_YUV_target))
{
if (!EmulateYUVBuiltIns(this, root, &getSymbolTable()))
{
return false;
}
if (!ReswizzleYUVTextureAccess(this, root, &getSymbolTable()))
{
return false;
}
}
switch (packedShaderType)
{
case gl::ShaderType::Fragment:
{
bool usesPointCoord = false;
bool usesFragCoord = false;
bool usesSampleMaskIn = false;
bool useSamplePosition = false;
// Search for the gl_PointCoord usage, if its used, we need to flip the y coordinate.
for (const ShaderVariable &inputVarying : mInputVaryings)
{
if (!inputVarying.isBuiltIn())
{
continue;
}
if (inputVarying.name == "gl_SampleMaskIn")
{
usesSampleMaskIn = true;
continue;
}
if (inputVarying.name == "gl_SamplePosition")
{
useSamplePosition = true;
continue;
}
if (inputVarying.name == "gl_PointCoord")
{
usesPointCoord = true;
break;
}
if (inputVarying.name == "gl_FragCoord")
{
usesFragCoord = true;
break;
}
}
bool hasGLSampleMask = false;
bool hasGLSecondaryFragData = false;
const TIntermSymbol *yuvOutput = nullptr;
for (const ShaderVariable &outputVar : mOutputVariables)
{
if (outputVar.name == "gl_SampleMask")
{
ASSERT(!hasGLSampleMask);
hasGLSampleMask = true;
continue;
}
if (outputVar.name == "gl_SecondaryFragDataEXT")
{
ASSERT(!hasGLSecondaryFragData);
hasGLSecondaryFragData = true;
continue;
}
if (outputVar.yuv)
{
// We can only have one yuv output
ASSERT(yuvOutput == nullptr);
yuvOutput = FindSymbolNode(root, ImmutableString(outputVar.name));
continue;
}
}
if (usesPointCoord)
{
TIntermTyped *flipNegXY =
driverUniforms->getNegFlipXY(&getSymbolTable(), DriverUniformFlip::Fragment);
TIntermConstantUnion *pivot = CreateFloatNode(0.5f, EbpMedium);
TIntermTyped *swapXY = specConst->getSwapXY();
if (swapXY == nullptr)
{
swapXY = driverUniforms->getSwapXY();
}
if (!RotateAndFlipBuiltinVariable(
this, root, GetMainSequence(root), swapXY, flipNegXY, &getSymbolTable(),
BuiltInVariable::gl_PointCoord(), kFlippedPointCoordName, pivot))
{
return false;
}
}
if (useSamplePosition)
{
TIntermTyped *flipXY =
driverUniforms->getFlipXY(&getSymbolTable(), DriverUniformFlip::Fragment);
TIntermConstantUnion *pivot = CreateFloatNode(0.5f, EbpMedium);
TIntermTyped *swapXY = specConst->getSwapXY();
if (swapXY == nullptr)
{
swapXY = driverUniforms->getSwapXY();
}
const TVariable *samplePositionBuiltin =
static_cast<const TVariable *>(getSymbolTable().findBuiltIn(
ImmutableString("gl_SamplePosition"), getShaderVersion()));
if (!RotateAndFlipBuiltinVariable(this, root, GetMainSequence(root), swapXY, flipXY,
&getSymbolTable(), samplePositionBuiltin,
kFlippedPointCoordName, pivot))
{
return false;
}
}
if (usesFragCoord)
{
if (!InsertFragCoordCorrection(this, compileOptions, root, GetMainSequence(root),
&getSymbolTable(), specConst, driverUniforms))
{
return false;
}
}
// Emulate gl_FragColor and gl_FragData with normal output variables.
if (!EmulateFragColorData(this, root, &getSymbolTable(), hasGLSecondaryFragData))
{
return false;
}
InputAttachmentMap inputAttachmentMap;
// Emulate framebuffer fetch if used.
if (HasFramebufferFetch(getExtensionBehavior(), compileOptions))
{
if (!EmulateFramebufferFetch(this, root, &inputAttachmentMap))
{
return false;
}
}
// This should be operated after doing ReplaceLastFragData and ReplaceInOutVariables,
// because they will create the input attachment variables. AddBlendMainCaller will
// check the existing input attachment variables and if there is no existing input
// attachment variable then create a new one.
if (getAdvancedBlendEquations().any() &&
compileOptions.addAdvancedBlendEquationsEmulation &&
!EmulateAdvancedBlendEquations(this, root, &getSymbolTable(),
getAdvancedBlendEquations(), driverUniforms,
&inputAttachmentMap))
{
return false;
}
// Input attachments are potentially added in framebuffer fetch and advanced blend
// emulation. Declare their SPIR-V ids.
assignInputAttachmentIds(inputAttachmentMap);
if (!RewriteDfdy(this, root, &getSymbolTable(), getShaderVersion(), specConst,
driverUniforms))
{
return false;
}
if (!RewriteInterpolateAtOffset(this, root, &getSymbolTable(), getShaderVersion(),
specConst, driverUniforms))
{
return false;
}
if (usesSampleMaskIn && !RewriteSampleMaskIn(this, root, &getSymbolTable()))
{
return false;
}
if (hasGLSampleMask)
{
TIntermTyped *numSamples = driverUniforms->getNumSamples();
if (!RewriteSampleMask(this, root, &getSymbolTable(), numSamples))
{
return false;
}
}
{
const TVariable *numSamplesVar =
static_cast<const TVariable *>(getSymbolTable().findBuiltIn(
ImmutableString("gl_NumSamples"), getShaderVersion()));
TIntermTyped *numSamples = driverUniforms->getNumSamples();
if (!ReplaceVariableWithTyped(this, root, numSamplesVar, numSamples))
{
return false;
}
}
if (IsExtensionEnabled(getExtensionBehavior(), TExtension::EXT_YUV_target))
{
if (yuvOutput != nullptr &&
!AdjustYUVOutput(this, root, &getSymbolTable(), *yuvOutput))
{
return false;
}
}
if (!EmulateDithering(this, compileOptions, root, &getSymbolTable(), specConst,
driverUniforms))
{
return false;
}
break;
}
case gl::ShaderType::Vertex:
{
if (compileOptions.addVulkanXfbEmulationSupportCode)
{
// Add support code for transform feedback emulation. Only applies to vertex shader
// as tessellation and geometry shader transform feedback capture require
// VK_EXT_transform_feedback.
if (!AddXfbEmulationSupport(this, root, &getSymbolTable(), driverUniforms))
{
return false;
}
}
break;
}
case gl::ShaderType::Geometry:
if (!ClampGLLayer(this, root, &getSymbolTable(), driverUniforms))
{
return false;
}
break;
case gl::ShaderType::TessControl:
{
if (!ReplaceGLBoundingBoxWithGlobal(this, root, &getSymbolTable(), getShaderVersion()))
{
return false;
}
break;
}
case gl::ShaderType::TessEvaluation:
break;
case gl::ShaderType::Compute:
break;
default:
UNREACHABLE();
break;
}
specConst->declareSpecConsts(root);
mValidateASTOptions.validateSpecConstReferences = true;
// Gather specialization constant usage bits so that we can feedback to context.
mSpecConstUsageBits = specConst->getSpecConstUsageBits();
if (!validateAST(root))
{
return false;
}
// Make sure function call validation is not accidentally left off anywhere.
ASSERT(mValidateASTOptions.validateFunctionCall);
ASSERT(mValidateASTOptions.validateNoRawFunctionCalls);
// 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.
const TVariable *inputPerVertex = nullptr;
const TVariable *outputPerVertex = nullptr;
if (!DeclarePerVertexBlocks(this, root, &getSymbolTable(), &inputPerVertex, &outputPerVertex))
{
return false;
}
if (inputPerVertex)
{
assignSpirvId(inputPerVertex->getType().getInterfaceBlock()->uniqueId(),
vk::spirv::kIdInputPerVertexBlock);
}
if (outputPerVertex)
{
assignSpirvId(outputPerVertex->getType().getInterfaceBlock()->uniqueId(),
vk::spirv::kIdOutputPerVertexBlock);
assignSpirvId(outputPerVertex->uniqueId(), vk::spirv::kIdOutputPerVertexVar);
}
// Now that all transformations are done, assign SPIR-V ids to whatever shader variable is still
// present in the shader in some form. This should be the last thing done in this function.
assignSpirvIds(root);
return true;
}
bool TranslatorSPIRV::translate(TIntermBlock *root,
const ShCompileOptions &compileOptions,
PerformanceDiagnostics *perfDiagnostics)
{
mUniqueToSpirvIdMap.clear();
mFirstUnusedSpirvId = 0;
SpecConst specConst(&getSymbolTable(), compileOptions, getShaderType());
DriverUniform driverUniforms(DriverUniformMode::InterfaceBlock);
DriverUniformExtended driverUniformsExt(DriverUniformMode::InterfaceBlock);
const bool useExtendedDriverUniforms = compileOptions.addVulkanXfbEmulationSupportCode;
DriverUniform *uniforms = useExtendedDriverUniforms ? &driverUniformsExt : &driverUniforms;
if (!translateImpl(root, compileOptions, perfDiagnostics, &specConst, uniforms))
{
return false;
}
return OutputSPIRV(this, root, compileOptions, mUniqueToSpirvIdMap, mFirstUnusedSpirvId);
}
bool TranslatorSPIRV::shouldFlattenPragmaStdglInvariantAll()
{
// Not necessary.
return false;
}
void TranslatorSPIRV::assignSpirvId(TSymbolUniqueId uniqueId, uint32_t spirvId)
{
ASSERT(mUniqueToSpirvIdMap.find(uniqueId.get()) == mUniqueToSpirvIdMap.end());
mUniqueToSpirvIdMap[uniqueId.get()] = spirvId;
}
void TranslatorSPIRV::assignInputAttachmentIds(const InputAttachmentMap &inputAttachmentMap)
{
for (auto &iter : inputAttachmentMap.color)
{
const uint32_t index = iter.first;
const TVariable *var = iter.second;
ASSERT(var != nullptr);
assignSpirvId(var->uniqueId(), vk::spirv::kIdInputAttachment0 + index);
const MetadataFlags flag = static_cast<MetadataFlags>(
static_cast<uint32_t>(MetadataFlags::HasInputAttachment0) + index);
mMetadataFlags.set(flag);
}
if (inputAttachmentMap.depth != nullptr)
{
assignSpirvId(inputAttachmentMap.depth->uniqueId(), vk::spirv::kIdDepthInputAttachment);
mMetadataFlags.set(MetadataFlags::HasDepthInputAttachment);
}
if (inputAttachmentMap.stencil != nullptr)
{
assignSpirvId(inputAttachmentMap.stencil->uniqueId(), vk::spirv::kIdStencilInputAttachment);
mMetadataFlags.set(MetadataFlags::HasStencilInputAttachment);
}
}
void TranslatorSPIRV::assignSpirvIds(TIntermBlock *root)
{
// Match the declarations with collected variables and assign a new id to each, starting from
// the first unreserved id. This makes sure that the reserved ids for internal variables and
// ids for shader variables form a minimal contiguous range. The Vulkan backend takes advantage
// of this fact for optimal hashing.
mFirstUnusedSpirvId = vk::spirv::kIdFirstUnreserved;
for (TIntermNode *node : *root->getSequence())
{
TIntermDeclaration *decl = node->getAsDeclarationNode();
if (decl == nullptr)
{
continue;
}
TIntermSymbol *symbol = decl->getSequence()->front()->getAsSymbolNode();
if (symbol == nullptr)
{
continue;
}
const TType &type = symbol->getType();
const TQualifier qualifier = type.getQualifier();
// Skip internal symbols, which already have a reserved id.
const TSymbolUniqueId uniqueId =
type.isInterfaceBlock() ? type.getInterfaceBlock()->uniqueId() : symbol->uniqueId();
if (mUniqueToSpirvIdMap.find(uniqueId.get()) != mUniqueToSpirvIdMap.end())
{
continue;
}
uint32_t *variableId = nullptr;
std::vector<ShaderVariable> *fields = nullptr;
if (type.isInterfaceBlock())
{
if (IsVaryingIn(qualifier))
{
ShaderVariable *varying =
FindIOBlockShaderVariable(&mInputVaryings, type.getInterfaceBlock()->name());
variableId = &varying->id;
fields = &varying->fields;
}
else if (IsVaryingOut(qualifier))
{
ShaderVariable *varying =
FindIOBlockShaderVariable(&mOutputVaryings, type.getInterfaceBlock()->name());
variableId = &varying->id;
fields = &varying->fields;
}
else if (IsStorageBuffer(qualifier))
{
InterfaceBlock *block =
FindShaderVariable(&mShaderStorageBlocks, type.getInterfaceBlock()->name());
variableId = &block->id;
}
else
{
InterfaceBlock *block =
FindShaderVariable(&mUniformBlocks, type.getInterfaceBlock()->name());
variableId = &block->id;
}
}
else if (qualifier == EvqUniform)
{
ShaderVariable *uniform = FindUniformShaderVariable(&mUniforms, symbol->getName());
variableId = &uniform->id;
}
else if (qualifier == EvqAttribute || qualifier == EvqVertexIn)
{
ShaderVariable *attribute = FindShaderVariable(&mAttributes, symbol->getName());
variableId = &attribute->id;
}
else if (IsShaderIn(qualifier))
{
ShaderVariable *varying = FindShaderVariable(&mInputVaryings, symbol->getName());
variableId = &varying->id;
fields = &varying->fields;
}
else if (qualifier == EvqFragmentOut)
{
// webgl_FragColor, webgl_FragData, webgl_SecondaryFragColor and webgl_SecondaryFragData
// are recorded with their original names (starting with gl_)
ImmutableString name(symbol->getName());
if (angle::BeginsWith(name.data(), "webgl_") &&
symbol->variable().symbolType() == SymbolType::AngleInternal)
{
name = ImmutableString(name.data() + 3, name.length() - 3);
}
ShaderVariable *output = FindShaderVariable(&mOutputVariables, name);
variableId = &output->id;
}
else if (IsShaderOut(qualifier))
{
ShaderVariable *varying = FindShaderVariable(&mOutputVaryings, symbol->getName());
variableId = &varying->id;
fields = &varying->fields;
}
if (variableId == nullptr)
{
continue;
}
ASSERT(variableId != nullptr);
assignSpirvId(uniqueId, mFirstUnusedSpirvId);
*variableId = mFirstUnusedSpirvId;
// Propagate the id to the first field of structs/blocks too. The front-end gathers
// varyings as fields, and the transformer needs to infer the variable id (of struct type)
// just by looking at the fields.
if (fields != nullptr)
{
SetSpirvIdInFields(mFirstUnusedSpirvId, fields);
}
++mFirstUnusedSpirvId;
}
}
} // namespace sh