blob: c482cd65d91e29d4ea361e21e6a6ee745a6817f1 [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.
//
// TranslatorVulkan:
// A GLSL-based translator that outputs shaders that fit GL_KHR_vulkan_glsl and feeds them into
// glslang to spit out SPIR-V.
// See: https://www.khronos.org/registry/vulkan/specs/misc/GL_KHR_vulkan_glsl.txt
//
#include "compiler/translator/TranslatorVulkan.h"
#include "angle_gl.h"
#include "common/PackedEnums.h"
#include "common/utilities.h"
#include "compiler/translator/BuiltinsWorkaroundGLSL.h"
#include "compiler/translator/ImmutableStringBuilder.h"
#include "compiler/translator/IntermNode.h"
#include "compiler/translator/OutputSPIRV.h"
#include "compiler/translator/OutputVulkanGLSL.h"
#include "compiler/translator/StaticType.h"
#include "compiler/translator/glslang_wrapper.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/RewriteCubeMapSamplersAs2DArray.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/vulkan/DeclarePerVertexBlocks.h"
#include "compiler/translator/tree_ops/vulkan/EmulateFragColorData.h"
#include "compiler/translator/tree_ops/vulkan/FlagSamplersWithTexelFetch.h"
#include "compiler/translator/tree_ops/vulkan/ReplaceForShaderFramebufferFetch.h"
#include "compiler/translator/tree_ops/vulkan/RewriteInterpolateAtOffset.h"
#include "compiler/translator/tree_ops/vulkan/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/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/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 gl::ShaderMap<const char *> kDefaultUniformNames = {
{gl::ShaderType::Vertex, vk::kDefaultUniformsNameVS},
{gl::ShaderType::TessControl, vk::kDefaultUniformsNameTCS},
{gl::ShaderType::TessEvaluation, vk::kDefaultUniformsNameTES},
{gl::ShaderType::Geometry, vk::kDefaultUniformsNameGS},
{gl::ShaderType::Fragment, vk::kDefaultUniformsNameFS},
{gl::ShaderType::Compute, vk::kDefaultUniformsNameCS},
};
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(TCompiler *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,
ImmutableString(kDefaultUniformNames[shaderType]), ImmutableString(""));
// 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.
ANGLE_NO_DISCARD bool RotateAndFlipBuiltinVariable(TCompiler *compiler,
TIntermBlock *root,
TIntermSequence *insertSequence,
TIntermTyped *flipXY,
TSymbolTable *symbolTable,
const TVariable *builtin,
const ImmutableString &flippedVariableName,
TIntermTyped *pivot,
TIntermTyped *fragRotation)
{
// Create a symbol reference to 'builtin'.
TIntermSymbol *builtinRef = new TIntermSymbol(builtin);
// Create a swizzle to "builtin.xy"
TVector<int> swizzleOffsetXY = {0, 1};
TIntermSwizzle *builtinXY = new TIntermSwizzle(builtinRef, swizzleOffsetXY);
// Create a symbol reference to our new variable that will hold the modified builtin.
const TType *type = StaticType::GetForVec<EbtFloat>(
EvqGlobal, static_cast<unsigned char>(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 "(builtin.xy * fragRotation)"
TIntermTyped *rotatedXY;
if (fragRotation)
{
rotatedXY = new TIntermBinary(EOpMatrixTimesVector, fragRotation, builtinXY);
}
else
{
// No rotation applied, use original variable.
rotatedXY = builtinXY;
}
// Create the expression "(builtin.xy - pivot) * flipXY + pivot
TIntermBinary *removePivot = new TIntermBinary(EOpSub, rotatedXY, 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(), swizzleOffsetXY);
TIntermBinary *assignToY = new TIntermBinary(EOpAssign, correctedXY, plusPivot);
// Add this assigment at the beginning of the main function
insertSequence->insert(insertSequence->begin(), assignToY);
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.
ANGLE_NO_DISCARD 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
TIntermBinary *angleEmulatedDepthRangeRef = driverUniforms->getDepthRangeRef();
// 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.
ANGLE_NO_DISCARD bool ReplaceGLBoundingBoxWithGlobal(TCompiler *compiler,
TIntermBlock *root,
TSymbolTable *symbolTable)
{
// Create a symbol reference to "gl_BoundingBoxEXT"
const TVariable *builtinBoundingBoxVar = static_cast<const TVariable *>(
symbolTable->findBuiltIn(ImmutableString("gl_BoundingBoxEXT"), 310));
if (builtinBoundingBoxVar != nullptr)
{
// Declare the replacement bounding box variable type
TType *emulatedBoundingBoxDeclType = new TType(builtinBoundingBoxVar->getType());
emulatedBoundingBoxDeclType->setQualifier(EvqGlobal);
TVariable *ANGLEBoundingBoxVar =
new TVariable(symbolTable->nextUniqueId(), ImmutableString("ANGLEBoundingBox"),
SymbolType::AngleInternal, TExtension::EXT_primitive_bounding_box,
emulatedBoundingBoxDeclType);
DeclareGlobalVariable(root, ANGLEBoundingBoxVar);
// Use the replacement variable instead of builtin gl_BoundingBoxEXT everywhere.
return ReplaceVariable(compiler, root, builtinBoundingBoxVar, ANGLEBoundingBoxVar);
}
return true;
}
TVariable *AddANGLEPositionVaryingDeclaration(TIntermBlock *root,
TSymbolTable *symbolTable,
TQualifier qualifier)
{
// Define a vec2 driver varying to hold the line rasterization emulation position.
TType *varyingType = new TType(EbtFloat, EbpMedium, qualifier, 2);
TVariable *varyingVar =
new TVariable(symbolTable, ImmutableString(vk::kLineRasterEmulationPosition), varyingType,
SymbolType::AngleInternal);
TIntermSymbol *varyingDeclarator = new TIntermSymbol(varyingVar);
TIntermDeclaration *varyingDecl = new TIntermDeclaration;
varyingDecl->appendDeclarator(varyingDeclarator);
TIntermSequence insertSequence;
insertSequence.push_back(varyingDecl);
// Insert the declarations before Main.
size_t mainIndex = FindMainIndex(root);
root->insertChildNodes(mainIndex, insertSequence);
return varyingVar;
}
ANGLE_NO_DISCARD bool AddBresenhamEmulationVS(TCompiler *compiler,
TIntermBlock *root,
TSymbolTable *symbolTable,
SpecConst *specConst,
const DriverUniform *driverUniforms)
{
TVariable *anglePosition = AddANGLEPositionVaryingDeclaration(root, symbolTable, EvqVaryingOut);
// Clamp position to subpixel grid.
// Do perspective divide (get normalized device coords)
// "vec2 ndc = gl_Position.xy / gl_Position.w"
const TType *vec2Type = StaticType::GetTemporary<EbtFloat, 2>();
TIntermBinary *viewportRef = driverUniforms->getViewportRef();
TIntermSymbol *glPos = new TIntermSymbol(BuiltInVariable::gl_Position());
TIntermSwizzle *glPosXY = CreateSwizzle(glPos, 0, 1);
TIntermSwizzle *glPosW = CreateSwizzle(glPos->deepCopy(), 3);
TVariable *ndc = CreateTempVariable(symbolTable, vec2Type);
TIntermBinary *noPerspective = new TIntermBinary(EOpDiv, glPosXY, glPosW);
TIntermDeclaration *ndcDecl = CreateTempInitDeclarationNode(ndc, noPerspective);
// Convert NDC to window coordinates. According to Vulkan spec.
// "vec2 window = 0.5 * viewport.wh * (ndc + 1) + viewport.xy"
TIntermBinary *ndcPlusOne =
new TIntermBinary(EOpAdd, CreateTempSymbolNode(ndc), CreateFloatNode(1.0f));
TIntermSwizzle *viewportZW = CreateSwizzle(viewportRef, 2, 3);
TIntermBinary *ndcViewport = new TIntermBinary(EOpMul, viewportZW, ndcPlusOne);
TIntermBinary *ndcViewportHalf =
new TIntermBinary(EOpVectorTimesScalar, ndcViewport, CreateFloatNode(0.5f));
TIntermSwizzle *viewportXY = CreateSwizzle(viewportRef->deepCopy(), 0, 1);
TIntermBinary *ndcToWindow = new TIntermBinary(EOpAdd, ndcViewportHalf, viewportXY);
TVariable *windowCoords = CreateTempVariable(symbolTable, vec2Type);
TIntermDeclaration *windowDecl = CreateTempInitDeclarationNode(windowCoords, ndcToWindow);
// Clamp to subpixel grid.
// "vec2 clamped = round(window * 2^{subpixelBits}) / 2^{subpixelBits}"
int subpixelBits = compiler->getResources().SubPixelBits;
TIntermConstantUnion *scaleConstant = CreateFloatNode(static_cast<float>(1 << subpixelBits));
TIntermBinary *windowScaled =
new TIntermBinary(EOpVectorTimesScalar, CreateTempSymbolNode(windowCoords), scaleConstant);
TIntermTyped *windowRounded =
CreateBuiltInUnaryFunctionCallNode("round", windowScaled, *symbolTable, 300);
TIntermBinary *windowRoundedBack =
new TIntermBinary(EOpDiv, windowRounded, scaleConstant->deepCopy());
TVariable *clampedWindowCoords = CreateTempVariable(symbolTable, vec2Type);
TIntermDeclaration *clampedDecl =
CreateTempInitDeclarationNode(clampedWindowCoords, windowRoundedBack);
// Set varying.
// "ANGLEPosition = 2 * (clamped - viewport.xy) / viewport.wh - 1"
TIntermBinary *clampedOffset = new TIntermBinary(
EOpSub, CreateTempSymbolNode(clampedWindowCoords), viewportXY->deepCopy());
TIntermBinary *clampedOff2x =
new TIntermBinary(EOpVectorTimesScalar, clampedOffset, CreateFloatNode(2.0f));
TIntermBinary *clampedDivided = new TIntermBinary(EOpDiv, clampedOff2x, viewportZW->deepCopy());
TIntermBinary *clampedNDC = new TIntermBinary(EOpSub, clampedDivided, CreateFloatNode(1.0f));
TIntermSymbol *varyingRef = new TIntermSymbol(anglePosition);
TIntermBinary *varyingAssign = new TIntermBinary(EOpAssign, varyingRef, clampedNDC);
TIntermBlock *emulationBlock = new TIntermBlock;
emulationBlock->appendStatement(ndcDecl);
emulationBlock->appendStatement(windowDecl);
emulationBlock->appendStatement(clampedDecl);
emulationBlock->appendStatement(varyingAssign);
TIntermIfElse *ifEmulation =
new TIntermIfElse(specConst->getLineRasterEmulation(), emulationBlock, nullptr);
// Ensure the statements run at the end of the main() function.
return RunAtTheEndOfShader(compiler, root, ifEmulation, symbolTable);
}
ANGLE_NO_DISCARD bool AddXfbEmulationSupport(TCompiler *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, 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());
TIntermBinary *xfbVerticesPerInstance = driverUniforms->getXfbVerticesPerInstance();
TIntermBinary *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(vk::kXfbEmulationGetOffsetsFunctionName),
SymbolType::AngleInternal, ivec4Type, true);
getOffsetsFunction->addParameter(stridesVar);
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>();
// Create the function body, which is empty.
body = new TIntermBlock;
// Declare the function
TFunction *xfbCaptureFunction =
new TFunction(symbolTable, ImmutableString(vk::kXfbEmulationCaptureFunctionName),
SymbolType::AngleInternal, voidType, false);
// Insert the function declaration before main().
root->insertChildNodes(mainIndex,
{CreateInternalFunctionDefinitionNode(*xfbCaptureFunction, body)});
// Create the following logic and add it at the end of main():
//
// if (ANGLEUniforms.xfbActiveUnpaused)
// {
// ANGLECaptureXfb();
// }
//
// Create a reference ANGLEUniforms.xfbActiveUnpaused
TIntermBinary *xfbActiveUnpaused = driverUniforms->getXfbActiveUnpaused();
// ANGLEUniforms.xfbActiveUnpaused != 0
TIntermBinary *isXfbActiveUnpaused =
new TIntermBinary(EOpNotEqual, xfbActiveUnpaused, CreateUIntNode(0));
// Create the function call
TIntermAggregate *captureXfbCall =
TIntermAggregate::CreateFunctionCall(*xfbCaptureFunction, {});
TIntermBlock *captureXfbBlock = new TIntermBlock;
captureXfbBlock->appendStatement(captureXfbCall);
// Create a call to ANGLEGetXfbOffsets too, for the sole purpose of preventing it from being
// culled as unused by glslang.
TIntermSequence ivec4Zero;
ivec4Zero.push_back(CreateZeroNode(*ivec4Type));
TIntermAggregate *getOffsetsCall =
TIntermAggregate::CreateFunctionCall(*getOffsetsFunction, &ivec4Zero);
captureXfbBlock->appendStatement(getOffsetsCall);
// Create the if
TIntermIfElse *captureXfb = new TIntermIfElse(isXfbActiveUnpaused, captureXfbBlock, nullptr);
// Run it at the end of the shader.
if (!RunAtTheEndOfShader(compiler, root, captureXfb, 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);
xfbOutType->makeArray(0);
TField *field = new TField(xfbOutType, ImmutableString(vk::kXfbEmulationBufferFieldName),
TSourceLoc(), SymbolType::AngleInternal);
fieldList->push_back(field);
static_assert(
kMaxXfbBuffers < 10,
"ImmutableStringBuilder memory size below needs to accomodate the number of buffers");
ImmutableStringBuilder blockName(strlen(vk::kXfbEmulationBufferBlockName) + 2);
blockName << vk::kXfbEmulationBufferBlockName;
blockName.appendDecimal(bufferIndex);
ImmutableStringBuilder varName(strlen(vk::kXfbEmulationBufferName) + 2);
varName << vk::kXfbEmulationBufferName;
varName.appendDecimal(bufferIndex);
TLayoutQualifier layoutQualifier = TLayoutQualifier::Create();
layoutQualifier.blockStorage = EbsStd430;
DeclareInterfaceBlock(root, symbolTable, fieldList, EvqBuffer, layoutQualifier,
TMemoryQualifier::Create(), 0, blockName, varName);
}
return compiler->validateAST(root);
}
ANGLE_NO_DISCARD bool AddXfbExtensionSupport(TCompiler *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(vk::kXfbExtensionPositionOutName), vec4Type,
SymbolType::AngleInternal);
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);
}
ANGLE_NO_DISCARD bool InsertFragCoordCorrection(TCompiler *compiler,
ShCompileOptions compileOptions,
TIntermBlock *root,
TIntermSequence *insertSequence,
TSymbolTable *symbolTable,
SpecConst *specConst,
const DriverUniform *driverUniforms)
{
TIntermTyped *flipXY = specConst->getFlipXY();
if (!flipXY)
{
flipXY = driverUniforms->getFlipXYRef();
}
TIntermBinary *pivot = specConst->getHalfRenderArea();
if (!pivot)
{
pivot = driverUniforms->getHalfRenderAreaRef();
}
TIntermTyped *fragRotation = nullptr;
if ((compileOptions & SH_ADD_PRE_ROTATION) != 0)
{
fragRotation = specConst->getFragRotationMatrix();
if (!fragRotation)
{
fragRotation = driverUniforms->getFragRotationMatrixRef();
}
}
return RotateAndFlipBuiltinVariable(compiler, root, insertSequence, flipXY, symbolTable,
BuiltInVariable::gl_FragCoord(), kFlippedFragCoordName,
pivot, fragRotation);
}
// This block adds OpenGL line segment rasterization emulation behind a specialization constant
// guard. OpenGL's simple rasterization algorithm is a strict subset of the pixels generated by the
// Vulkan algorithm. Thus we can implement a shader patch that rejects pixels if they would not be
// generated by the OpenGL algorithm. OpenGL's algorithm is similar to Bresenham's line algorithm.
// It is implemented for each pixel by testing if the line segment crosses a small diamond inside
// the pixel. See the OpenGL ES 2.0 spec section "3.4.1 Basic Line Segment Rasterization". Also
// see the Vulkan spec section "24.6.1. Basic Line Segment Rasterization":
// https://khronos.org/registry/vulkan/specs/1.0/html/vkspec.html#primsrast-lines-basic
//
// Using trigonometric math and the fact that we know the size of the diamond we can derive a
// formula to test if the line segment crosses the pixel center. gl_FragCoord is used along with an
// internal position varying to determine the inputs to the formula.
//
// The implementation of the test is similar to the following pseudocode:
//
// void main()
// {
// vec2 p = (((((ANGLEPosition.xy) * 0.5) + 0.5) * viewport.zw) + viewport.xy);
// vec2 d = dFdx(p) + dFdy(p);
// vec2 f = gl_FragCoord.xy;
// vec2 p_ = p.yx;
// vec2 d_ = d.yx;
// vec2 f_ = f.yx;
//
// vec2 i = abs(p - f + (d / d_) * (f_ - p_));
//
// if (i.x > (0.5 + e) && i.y > (0.5 + e))
// discard;
// <otherwise run fragment shader main>
// }
//
// Note this emulation can not provide fully correct rasterization. See the docs more more info.
ANGLE_NO_DISCARD bool AddBresenhamEmulationFS(TCompiler *compiler,
ShCompileOptions compileOptions,
TIntermBlock *root,
TSymbolTable *symbolTable,
SpecConst *specConst,
const DriverUniform *driverUniforms,
bool usesFragCoord)
{
TVariable *anglePosition = AddANGLEPositionVaryingDeclaration(root, symbolTable, EvqVaryingIn);
const TType *vec2Type = StaticType::GetTemporary<EbtFloat, 2>();
TIntermBinary *viewportRef = driverUniforms->getViewportRef();
// vec2 p = ((ANGLEPosition * 0.5) + 0.5) * viewport.zw + viewport.xy
TIntermSwizzle *viewportXY = CreateSwizzle(viewportRef->deepCopy(), 0, 1);
TIntermSwizzle *viewportZW = CreateSwizzle(viewportRef, 2, 3);
TIntermSymbol *position = new TIntermSymbol(anglePosition);
TIntermConstantUnion *oneHalf = CreateFloatNode(0.5f);
TIntermBinary *halfPosition = new TIntermBinary(EOpVectorTimesScalar, position, oneHalf);
TIntermBinary *offsetHalfPosition =
new TIntermBinary(EOpAdd, halfPosition, oneHalf->deepCopy());
TIntermBinary *scaledPosition = new TIntermBinary(EOpMul, offsetHalfPosition, viewportZW);
TIntermBinary *windowPosition = new TIntermBinary(EOpAdd, scaledPosition, viewportXY);
TVariable *p = CreateTempVariable(symbolTable, vec2Type);
TIntermDeclaration *pDecl = CreateTempInitDeclarationNode(p, windowPosition);
// vec2 d = dFdx(p) + dFdy(p)
TIntermTyped *dfdx =
CreateBuiltInUnaryFunctionCallNode("dFdx", new TIntermSymbol(p), *symbolTable, 300);
TIntermTyped *dfdy =
CreateBuiltInUnaryFunctionCallNode("dFdy", new TIntermSymbol(p), *symbolTable, 300);
TIntermBinary *dfsum = new TIntermBinary(EOpAdd, dfdx, dfdy);
TVariable *d = CreateTempVariable(symbolTable, vec2Type);
TIntermDeclaration *dDecl = CreateTempInitDeclarationNode(d, dfsum);
// vec2 f = gl_FragCoord.xy
const TVariable *fragCoord = BuiltInVariable::gl_FragCoord();
TIntermSwizzle *fragCoordXY = CreateSwizzle(new TIntermSymbol(fragCoord), 0, 1);
TVariable *f = CreateTempVariable(symbolTable, vec2Type);
TIntermDeclaration *fDecl = CreateTempInitDeclarationNode(f, fragCoordXY);
// vec2 p_ = p.yx
TIntermSwizzle *pyx = CreateSwizzle(new TIntermSymbol(p), 1, 0);
TVariable *p_ = CreateTempVariable(symbolTable, vec2Type);
TIntermDeclaration *p_decl = CreateTempInitDeclarationNode(p_, pyx);
// vec2 d_ = d.yx
TIntermSwizzle *dyx = CreateSwizzle(new TIntermSymbol(d), 1, 0);
TVariable *d_ = CreateTempVariable(symbolTable, vec2Type);
TIntermDeclaration *d_decl = CreateTempInitDeclarationNode(d_, dyx);
// vec2 f_ = f.yx
TIntermSwizzle *fyx = CreateSwizzle(new TIntermSymbol(f), 1, 0);
TVariable *f_ = CreateTempVariable(symbolTable, vec2Type);
TIntermDeclaration *f_decl = CreateTempInitDeclarationNode(f_, fyx);
// vec2 i = abs(p - f + (d/d_) * (f_ - p_))
TIntermBinary *dd = new TIntermBinary(EOpDiv, new TIntermSymbol(d), new TIntermSymbol(d_));
TIntermBinary *fp = new TIntermBinary(EOpSub, new TIntermSymbol(f_), new TIntermSymbol(p_));
TIntermBinary *ddfp = new TIntermBinary(EOpMul, dd, fp);
TIntermBinary *pf = new TIntermBinary(EOpSub, new TIntermSymbol(p), new TIntermSymbol(f));
TIntermBinary *expr = new TIntermBinary(EOpAdd, pf, ddfp);
TIntermTyped *absd = CreateBuiltInUnaryFunctionCallNode("abs", expr, *symbolTable, 100);
TVariable *i = CreateTempVariable(symbolTable, vec2Type);
TIntermDeclaration *iDecl = CreateTempInitDeclarationNode(i, absd);
// Using a small epsilon value ensures that we don't suffer from numerical instability when
// lines are exactly vertical or horizontal.
static constexpr float kEpsilon = 0.0001f;
static constexpr float kThreshold = 0.5 + kEpsilon;
TIntermConstantUnion *threshold = CreateFloatNode(kThreshold);
// if (i.x > (0.5 + e) && i.y > (0.5 + e))
TIntermSwizzle *ix = CreateSwizzle(new TIntermSymbol(i), 0);
TIntermBinary *checkX = new TIntermBinary(EOpGreaterThan, ix, threshold);
TIntermSwizzle *iy = CreateSwizzle(new TIntermSymbol(i), 1);
TIntermBinary *checkY = new TIntermBinary(EOpGreaterThan, iy, threshold->deepCopy());
TIntermBinary *checkXY = new TIntermBinary(EOpLogicalAnd, checkX, checkY);
// discard
TIntermBranch *discard = new TIntermBranch(EOpKill, nullptr);
TIntermBlock *discardBlock = new TIntermBlock;
discardBlock->appendStatement(discard);
TIntermIfElse *ifStatement = new TIntermIfElse(checkXY, discardBlock, nullptr);
TIntermBlock *emulationBlock = new TIntermBlock;
TIntermSequence *emulationSequence = emulationBlock->getSequence();
std::array<TIntermNode *, 8> nodes = {
{pDecl, dDecl, fDecl, p_decl, d_decl, f_decl, iDecl, ifStatement}};
emulationSequence->insert(emulationSequence->begin(), nodes.begin(), nodes.end());
TIntermIfElse *ifEmulation =
new TIntermIfElse(specConst->getLineRasterEmulation(), emulationBlock, nullptr);
// Ensure the line raster code runs at the beginning of main().
TIntermFunctionDefinition *main = FindMain(root);
TIntermSequence *mainSequence = main->getBody()->getSequence();
ASSERT(mainSequence);
mainSequence->insert(mainSequence->begin(), ifEmulation);
// If the shader does not use frag coord, we should insert it inside the emulation if.
if (!usesFragCoord)
{
if (!InsertFragCoordCorrection(compiler, compileOptions, root, emulationSequence,
symbolTable, specConst, driverUniforms))
{
return false;
}
}
return compiler->validateAST(root);
}
} // anonymous namespace
TranslatorVulkan::TranslatorVulkan(sh::GLenum type, ShShaderSpec spec)
: TCompiler(type, spec, SH_GLSL_450_CORE_OUTPUT)
{}
bool TranslatorVulkan::translateImpl(TInfoSinkBase &sink,
TIntermBlock *root,
ShCompileOptions compileOptions,
PerformanceDiagnostics * /*perfDiagnostics*/,
SpecConst *specConst,
DriverUniform *driverUniforms)
{
if (getShaderType() == GL_VERTEX_SHADER)
{
if (!ShaderBuiltinsWorkaround(this, root, &getSymbolTable(), compileOptions))
{
return false;
}
}
sink << "#version 450 core\n";
writeExtensionBehavior(compileOptions, sink);
WritePragma(sink, compileOptions, getPragma());
// 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 glslang wrapper doesn't need to
// replace them. Note: this is done before extracting samplers from structs, as removing such
// inactive samplers is not yet supported. Note also that currently, CollectVariables marks
// every field of an active uniform that's of struct type as active, i.e. no extracted sampler
// is inactive.
if (!RemoveInactiveInterfaceVariables(this, root, &getSymbolTable(), getAttributes(),
getInputVaryings(), getOutputVariables(), getUniforms(),
getInterfaceBlocks()))
{
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.
if (!MonomorphizeUnsupportedFunctions(this, root, &getSymbolTable(), compileOptions))
{
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;
}
// Rewrite samplerCubes as sampler2DArrays. This must be done after rewriting struct samplers
// as it doesn't expect that.
if ((compileOptions & SH_EMULATE_SEAMFUL_CUBE_MAP_SAMPLING) != 0)
{
if (!RewriteCubeMapSamplersAs2DArray(this, root, &getSymbolTable(),
getShaderType() == GL_FRAGMENT_SHADER))
{
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());
}
if (r32fImageCount > 0)
{
if (!RewriteR32fImages(this, root, &getSymbolTable()))
{
return false;
}
}
if (atomicCounterCount > 0)
{
// ANGLEUniforms.acbBufferOffsets
const TIntermTyped *acbBufferOffsets = driverUniforms->getAbcBufferOffsets();
if (!RewriteAtomicCounters(this, root, &getSymbolTable(), acbBufferOffsets))
{
return false;
}
}
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 & SH_ADD_VULKAN_XFB_EXTENSION_SUPPORT_CODE) != 0)
{
// Add support code for transform feedback extension.
if (!AddXfbExtensionSupport(this, root, &getSymbolTable(), driverUniforms))
{
return false;
}
}
}
switch (packedShaderType)
{
case gl::ShaderType::Fragment:
{
bool usesPointCoord = false;
bool usesFragCoord = false;
bool usesSampleMaskIn = false;
bool usesLastFragData = 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;
}
if (inputVarying.name == "gl_LastFragData")
{
usesLastFragData = true;
break;
}
}
if ((compileOptions & SH_ADD_BRESENHAM_LINE_RASTER_EMULATION) != 0)
{
if (!AddBresenhamEmulationFS(this, compileOptions, root, &getSymbolTable(),
specConst, driverUniforms, usesFragCoord))
{
return false;
}
}
bool usePreRotation = (compileOptions & SH_ADD_PRE_ROTATION) != 0;
bool hasGLSampleMask = false;
for (const ShaderVariable &outputVar : mOutputVariables)
{
if (outputVar.name == "gl_SampleMask")
{
ASSERT(!hasGLSampleMask);
hasGLSampleMask = true;
continue;
}
}
// Emulate gl_FragColor and gl_FragData with normal output variables.
if (!EmulateFragColorData(this, root, &getSymbolTable()))
{
return false;
}
if (usesPointCoord)
{
TIntermTyped *flipNegXY = specConst->getNegFlipXY();
if (!flipNegXY)
{
flipNegXY = driverUniforms->getNegFlipXYRef();
}
TIntermConstantUnion *pivot = CreateFloatNode(0.5f);
TIntermTyped *fragRotation = nullptr;
if (usePreRotation)
{
fragRotation = specConst->getFragRotationMatrix();
if (!fragRotation)
{
fragRotation = driverUniforms->getFragRotationMatrixRef();
}
}
if (!RotateAndFlipBuiltinVariable(this, root, GetMainSequence(root), flipNegXY,
&getSymbolTable(),
BuiltInVariable::gl_PointCoord(),
kFlippedPointCoordName, pivot, fragRotation))
{
return false;
}
}
if (useSamplePosition)
{
TIntermTyped *flipXY = specConst->getFlipXY();
if (!flipXY)
{
flipXY = driverUniforms->getFlipXYRef();
}
TIntermConstantUnion *pivot = CreateFloatNode(0.5f);
TIntermTyped *fragRotation = nullptr;
if (usePreRotation)
{
fragRotation = specConst->getFragRotationMatrix();
if (!fragRotation)
{
fragRotation = driverUniforms->getFragRotationMatrixRef();
}
}
if (!RotateAndFlipBuiltinVariable(this, root, GetMainSequence(root), flipXY,
&getSymbolTable(),
BuiltInVariable::gl_SamplePosition(),
kFlippedPointCoordName, pivot, fragRotation))
{
return false;
}
}
if (usesFragCoord)
{
if (!InsertFragCoordCorrection(this, compileOptions, root, GetMainSequence(root),
&getSymbolTable(), specConst, driverUniforms))
{
return false;
}
}
if (usesLastFragData && !ReplaceLastFragData(this, root, &getSymbolTable(), &mUniforms))
{
return false;
}
if (!ReplaceInOutVariables(this, root, &getSymbolTable(), &mUniforms))
{
return false;
}
if (!RewriteDfdy(this, compileOptions, root, getSymbolTable(), getShaderVersion(),
specConst, driverUniforms))
{
return false;
}
if (!RewriteInterpolateAtOffset(this, compileOptions, root, getSymbolTable(),
getShaderVersion(), specConst, driverUniforms))
{
return false;
}
if (usesSampleMaskIn && !RewriteSampleMaskIn(this, root, &getSymbolTable()))
{
return false;
}
if (hasGLSampleMask)
{
TIntermBinary *numSamples = driverUniforms->getNumSamplesRef();
if (!RewriteSampleMask(this, root, &getSymbolTable(), numSamples))
{
return false;
}
}
{
const TVariable *numSamplesVar =
static_cast<const TVariable *>(getSymbolTable().findBuiltIn(
ImmutableString("gl_NumSamples"), getShaderVersion()));
TIntermBinary *numSamples = driverUniforms->getNumSamplesRef();
if (!ReplaceVariableWithTyped(this, root, numSamplesVar, numSamples))
{
return false;
}
}
EmitEarlyFragmentTestsGLSL(*this, sink);
break;
}
case gl::ShaderType::Vertex:
{
if ((compileOptions & SH_ADD_BRESENHAM_LINE_RASTER_EMULATION) != 0)
{
if (!AddBresenhamEmulationVS(this, root, &getSymbolTable(), specConst,
driverUniforms))
{
return false;
}
}
if ((compileOptions & SH_ADD_VULKAN_XFB_EMULATION_SUPPORT_CODE) != 0)
{
// 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;
}
}
// Append depth range translation to main.
if (!transformDepthBeforeCorrection(root, driverUniforms))
{
return false;
}
break;
}
case gl::ShaderType::Geometry:
{
int maxVertices = getGeometryShaderMaxVertices();
// max_vertices=0 is not valid in Vulkan
maxVertices = std::max(1, maxVertices);
WriteGeometryShaderLayoutQualifiers(
sink, getGeometryShaderInputPrimitiveType(), getGeometryShaderInvocations(),
getGeometryShaderOutputPrimitiveType(), maxVertices);
break;
}
case gl::ShaderType::TessControl:
{
if (!ReplaceGLBoundingBoxWithGlobal(this, root, &getSymbolTable()))
{
return false;
}
WriteTessControlShaderLayoutQualifiers(sink, getTessControlShaderOutputVertices());
break;
}
case gl::ShaderType::TessEvaluation:
{
WriteTessEvaluationShaderLayoutQualifiers(
sink, getTessEvaluationShaderInputPrimitiveType(),
getTessEvaluationShaderInputVertexSpacingType(),
getTessEvaluationShaderInputOrderingType(),
getTessEvaluationShaderInputPointType());
break;
}
case gl::ShaderType::Compute:
{
EmitWorkGroupSizeGLSL(*this, sink);
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);
return true;
}
void TranslatorVulkan::writeExtensionBehavior(ShCompileOptions compileOptions, TInfoSinkBase &sink)
{
const TExtensionBehavior &extBehavior = getExtensionBehavior();
TBehavior multiviewBehavior = EBhUndefined;
TBehavior multiview2Behavior = EBhUndefined;
for (const auto &iter : extBehavior)
{
if (iter.second == EBhUndefined || iter.second == EBhDisable)
{
continue;
}
switch (iter.first)
{
case TExtension::OVR_multiview:
multiviewBehavior = iter.second;
break;
case TExtension::OVR_multiview2:
multiviewBehavior = iter.second;
break;
default:
break;
}
}
if (multiviewBehavior != EBhUndefined || multiview2Behavior != EBhUndefined)
{
// Only either OVR_multiview or OVR_multiview2 should be emitted.
TExtension ext = TExtension::OVR_multiview;
TBehavior behavior = multiviewBehavior;
if (multiview2Behavior != EBhUndefined)
{
ext = TExtension::OVR_multiview2;
behavior = multiview2Behavior;
}
EmitMultiviewGLSL(*this, compileOptions, ext, behavior, sink);
}
}
bool TranslatorVulkan::translate(TIntermBlock *root,
ShCompileOptions compileOptions,
PerformanceDiagnostics *perfDiagnostics)
{
TInfoSinkBase sink;
bool enablePrecision = (compileOptions & SH_IGNORE_PRECISION_QUALIFIERS) == 0;
SpecConst specConst(&getSymbolTable(), compileOptions, getShaderType());
if ((compileOptions & SH_USE_SPECIALIZATION_CONSTANT) != 0)
{
DriverUniform driverUniforms(DriverUniformMode::InterfaceBlock);
if (!translateImpl(sink, root, compileOptions, perfDiagnostics, &specConst,
&driverUniforms))
{
return false;
}
}
else
{
DriverUniformExtended driverUniformsExt(DriverUniformMode::InterfaceBlock);
if (!translateImpl(sink, root, compileOptions, perfDiagnostics, &specConst,
&driverUniformsExt))
{
return false;
}
}
#if defined(ANGLE_ENABLE_DIRECT_SPIRV_GENERATION)
if ((compileOptions & SH_GENERATE_SPIRV_DIRECTLY) != 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.
if (!DeclarePerVertexBlocks(this, root, &getSymbolTable()))
{
return false;
}
return OutputSPIRV(this, root, compileOptions);
}
#endif
// When generating text, glslang cannot know the precision of folded constants so it may infer
// the wrong precisions. The following transformation gives constants names with precision to
// guide glslang. This is not an issue for SPIR-V generation because the precision information
// is present in the tree already.
if (!RecordConstantPrecision(this, root, &getSymbolTable()))
{
return false;
}
// Write translated shader.
TOutputVulkanGLSL outputGLSL(this, sink, enablePrecision, compileOptions);
root->traverse(&outputGLSL);
return compileToSpirv(sink);
}
bool TranslatorVulkan::shouldFlattenPragmaStdglInvariantAll()
{
// Not necessary.
return false;
}
bool TranslatorVulkan::compileToSpirv(const TInfoSinkBase &glsl)
{
angle::spirv::Blob spirvBlob;
if (!GlslangCompileToSpirv(getResources(), getShaderType(), glsl.str(), &spirvBlob))
{
return false;
}
getInfoSink().obj.setBinary(std::move(spirvBlob));
return true;
}
} // namespace sh