blob: a1ce515f05638c71e60c6e58506faf4e35eaa9a9 [file] [log] [blame]
//
// Copyright 2020 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.
//
#include "compiler/translator/msl/TranslatorMSL.h"
#include "angle_gl.h"
#include "common/utilities.h"
#include "compiler/translator/ImmutableStringBuilder.h"
#include "compiler/translator/StaticType.h"
#include "compiler/translator/msl/AstHelpers.h"
#include "compiler/translator/msl/DriverUniformMetal.h"
#include "compiler/translator/msl/EmitMetal.h"
#include "compiler/translator/msl/Name.h"
#include "compiler/translator/msl/RewritePipelines.h"
#include "compiler/translator/msl/SymbolEnv.h"
#include "compiler/translator/msl/ToposortStructs.h"
#include "compiler/translator/msl/UtilsMSL.h"
#include "compiler/translator/tree_ops/InitializeVariables.h"
#include "compiler/translator/tree_ops/MonomorphizeUnsupportedFunctions.h"
#include "compiler/translator/tree_ops/PreTransformTextureCubeGradDerivatives.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/msl/AddExplicitTypeCasts.h"
#include "compiler/translator/tree_ops/msl/ConvertUnsupportedConstructorsToFunctionCalls.h"
#include "compiler/translator/tree_ops/msl/FixTypeConstructors.h"
#include "compiler/translator/tree_ops/msl/HoistConstants.h"
#include "compiler/translator/tree_ops/msl/IntroduceVertexIndexID.h"
#include "compiler/translator/tree_ops/msl/NameEmbeddedUniformStructsMetal.h"
#include "compiler/translator/tree_ops/msl/ReduceInterfaceBlocks.h"
#include "compiler/translator/tree_ops/msl/RewriteCaseDeclarations.h"
#include "compiler/translator/tree_ops/msl/RewriteInterpolants.h"
#include "compiler/translator/tree_ops/msl/RewriteOutArgs.h"
#include "compiler/translator/tree_ops/msl/RewriteUnaddressableReferences.h"
#include "compiler/translator/tree_ops/msl/SeparateCompoundExpressions.h"
#include "compiler/translator/tree_ops/msl/SeparateCompoundStructDeclarations.h"
#include "compiler/translator/tree_ops/msl/WrapMain.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/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 Name kFlippedPointCoordName("flippedPointCoord", SymbolType::AngleInternal);
constexpr Name kFlippedFragCoordName("flippedFragCoord", SymbolType::AngleInternal);
constexpr const TVariable kgl_VertexIDMetal(BuiltInId::gl_VertexID,
ImmutableString("gl_VertexID"),
SymbolType::BuiltIn,
TExtension::UNDEFINED,
StaticType::Get<EbtUInt, EbpHigh, EvqVertexID, 1, 1>());
class DeclareStructTypesTraverser : public TIntermTraverser
{
public:
explicit DeclareStructTypesTraverser(TOutputMSL *outputMSL)
: TIntermTraverser(true, false, false), mOutputMSL(outputMSL)
{}
bool visitDeclaration(Visit visit, TIntermDeclaration *node) override
{
ASSERT(visit == PreVisit);
if (!mInGlobalScope)
{
return false;
}
const TIntermSequence &sequence = *(node->getSequence());
TIntermTyped *declarator = sequence.front()->getAsTyped();
const TType &type = declarator->getType();
if (type.isStructSpecifier())
{
const TStructure *structure = type.getStruct();
// Embedded structs should be parsed away by now.
ASSERT(structure->symbolType() != SymbolType::Empty);
// outputMSL->writeStructType(structure);
TIntermSymbol *symbolNode = declarator->getAsSymbolNode();
if (symbolNode && symbolNode->variable().symbolType() == SymbolType::Empty)
{
// Remove the struct specifier declaration from the tree so it isn't parsed again.
TIntermSequence emptyReplacement;
mMultiReplacements.emplace_back(getParentNode()->getAsBlock(), node,
std::move(emptyReplacement));
}
}
// TODO: REMOVE, used to remove 'unsued' warning
mOutputMSL = nullptr;
return false;
}
private:
TOutputMSL *mOutputMSL;
};
class DeclareDefaultUniformsTraverser : public TIntermTraverser
{
public:
DeclareDefaultUniformsTraverser(TInfoSinkBase *sink,
ShHashFunction64 hashFunction,
NameMap *nameMap)
: TIntermTraverser(true, true, true),
mSink(sink),
mHashFunction(hashFunction),
mNameMap(nameMap),
mInDefaultUniform(false)
{}
bool visitDeclaration(Visit visit, TIntermDeclaration *node) override
{
const TIntermSequence &sequence = *(node->getSequence());
// TODO(jmadill): Compound declarations.
ASSERT(sequence.size() == 1);
TIntermTyped *variable = sequence.front()->getAsTyped();
const TType &type = variable->getType();
bool isUniform = type.getQualifier() == EvqUniform && !type.isInterfaceBlock() &&
!IsOpaqueType(type.getBasicType());
if (visit == PreVisit)
{
if (isUniform)
{
(*mSink) << " " << GetTypeName(type, mHashFunction, mNameMap) << " ";
mInDefaultUniform = true;
}
}
else if (visit == InVisit)
{
mInDefaultUniform = isUniform;
}
else if (visit == PostVisit)
{
if (isUniform)
{
(*mSink) << ";\n";
// Remove the uniform declaration from the tree so it isn't parsed again.
TIntermSequence emptyReplacement;
mMultiReplacements.emplace_back(getParentNode()->getAsBlock(), node,
std::move(emptyReplacement));
}
mInDefaultUniform = false;
}
return true;
}
void visitSymbol(TIntermSymbol *symbol) override
{
if (mInDefaultUniform)
{
const ImmutableString &name = symbol->variable().name();
ASSERT(!gl::IsBuiltInName(name.data()));
(*mSink) << HashName(&symbol->variable(), mHashFunction, mNameMap)
<< ArrayString(symbol->getType());
}
}
private:
TInfoSinkBase *mSink;
ShHashFunction64 mHashFunction;
NameMap *mNameMap;
bool mInDefaultUniform;
};
// Declares a new variable to replace gl_DepthRange, its values are fed from a driver uniform.
[[nodiscard]] bool ReplaceGLDepthRangeWithDriverUniform(TCompiler *compiler,
TIntermBlock *root,
const DriverUniformMetal *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);
}
TIntermSequence *GetMainSequence(TIntermBlock *root)
{
TIntermFunctionDefinition *main = FindMain(root);
return main->getBody()->getSequence();
}
// Replaces a builtin variable with a version that is rotated and corrects the X and Y coordinates.
[[nodiscard]] bool FlipBuiltinVariable(TCompiler *compiler,
TIntermBlock *root,
TIntermSequence *insertSequence,
TIntermTyped *flipXY,
TSymbolTable *symbolTable,
const TVariable *builtin,
const Name &flippedVariableName,
TIntermTyped *pivot)
{
// 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, EbpHigh>(EvqGlobal, builtin->getType().getNominalSize());
TVariable *replacementVar =
new TVariable(symbolTable, flippedVariableName.rawName(), 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 - 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.
TIntermSequence sequence;
sequence.push_back(builtinRef->deepCopy());
TIntermAggregate *aggregate =
TIntermAggregate::CreateConstructor(builtin->getType(), &sequence);
TIntermBinary *assignment = new TIntermBinary(EOpAssign, flippedBuiltinRef, aggregate);
// 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);
}
[[nodiscard]] bool InsertFragCoordCorrection(TCompiler *compiler,
const ShCompileOptions &compileOptions,
TIntermBlock *root,
TIntermSequence *insertSequence,
TSymbolTable *symbolTable,
const DriverUniformMetal *driverUniforms)
{
TIntermTyped *flipXY = driverUniforms->getFlipXY(symbolTable, DriverUniformFlip::Fragment);
TIntermTyped *pivot = driverUniforms->getHalfRenderArea();
const TVariable *fragCoord = static_cast<const TVariable *>(
symbolTable->findBuiltIn(ImmutableString("gl_FragCoord"), compiler->getShaderVersion()));
return FlipBuiltinVariable(compiler, root, insertSequence, flipXY, symbolTable, fragCoord,
kFlippedFragCoordName, pivot);
}
void DeclareRightBeforeMain(TIntermBlock &root, const TVariable &var)
{
root.insertChildNodes(FindMainIndex(&root), {new TIntermDeclaration{&var}});
}
void AddFragColorDeclaration(TIntermBlock &root, TSymbolTable &symbolTable, const TVariable &var)
{
root.insertChildNodes(FindMainIndex(&root), TIntermSequence{new TIntermDeclaration{&var}});
}
void AddFragDepthDeclaration(TIntermBlock &root, TSymbolTable &symbolTable)
{
// Check if the variable has been already declared.
const TIntermSymbol *fragDepthBuiltIn = new TIntermSymbol(BuiltInVariable::gl_FragDepth());
const TIntermSymbol *fragDepthSymbol = FindSymbolNode(&root, ImmutableString("gl_FragDepth"));
if (fragDepthSymbol && fragDepthSymbol->uniqueId() != fragDepthBuiltIn->uniqueId())
{
return;
}
root.insertChildNodes(FindMainIndex(&root),
TIntermSequence{new TIntermDeclaration{BuiltInVariable::gl_FragDepth()}});
}
void AddFragDepthEXTDeclaration(TCompiler &compiler, TIntermBlock &root, TSymbolTable &symbolTable)
{
const TIntermSymbol *glFragDepthExt = FindSymbolNode(&root, ImmutableString("gl_FragDepthEXT"));
ASSERT(glFragDepthExt);
// Replace gl_FragData with our globally defined fragdata.
if (!ReplaceVariable(&compiler, &root, &(glFragDepthExt->variable()),
BuiltInVariable::gl_FragDepth()))
{
return;
}
AddFragDepthDeclaration(root, symbolTable);
}
[[nodiscard]] bool AddNumSamplesDeclaration(TCompiler &compiler,
TIntermBlock &root,
TSymbolTable &symbolTable)
{
const TVariable *glNumSamples = BuiltInVariable::gl_NumSamples();
DeclareRightBeforeMain(root, *glNumSamples);
// gl_NumSamples = metal::get_num_samples();
TIntermBinary *assignment = new TIntermBinary(
TOperator::EOpAssign, new TIntermSymbol(glNumSamples),
CreateBuiltInFunctionCallNode("numSamples", {}, symbolTable, kESSLInternalBackendBuiltIns));
return RunAtTheBeginningOfShader(&compiler, &root, assignment);
}
[[nodiscard]] bool AddSamplePositionDeclaration(TCompiler &compiler,
TIntermBlock &root,
TSymbolTable &symbolTable,
const DriverUniformMetal *driverUniforms)
{
const TVariable *glSamplePosition = BuiltInVariable::gl_SamplePosition();
DeclareRightBeforeMain(root, *glSamplePosition);
// When rendering to a default FBO, gl_SamplePosition should
// be Y-flipped to match the actual sample location
// gl_SamplePosition = metal::get_sample_position(uint(gl_SampleID));
// gl_SamplePosition -= 0.5;
// gl_SamplePosition *= flipXY;
// gl_SamplePosition += 0.5;
TIntermBlock *block = new TIntermBlock;
block->appendStatement(new TIntermBinary(
TOperator::EOpAssign, new TIntermSymbol(glSamplePosition),
CreateBuiltInFunctionCallNode("samplePosition",
{TIntermAggregate::CreateConstructor(
*StaticType::GetBasic<EbtUInt, EbpHigh>(),
{new TIntermSymbol(BuiltInVariable::gl_SampleID())})},
symbolTable, kESSLInternalBackendBuiltIns)));
block->appendStatement(new TIntermBinary(TOperator::EOpSubAssign,
new TIntermSymbol(glSamplePosition),
CreateFloatNode(0.5f, EbpHigh)));
block->appendStatement(
new TIntermBinary(EOpMulAssign, new TIntermSymbol(glSamplePosition),
driverUniforms->getFlipXY(&symbolTable, DriverUniformFlip::Fragment)));
block->appendStatement(new TIntermBinary(TOperator::EOpAddAssign,
new TIntermSymbol(glSamplePosition),
CreateFloatNode(0.5f, EbpHigh)));
return RunAtTheBeginningOfShader(&compiler, &root, block);
}
[[nodiscard]] bool AddSampleMaskInDeclaration(TCompiler &compiler,
TIntermBlock &root,
TSymbolTable &symbolTable,
const DriverUniformMetal *driverUniforms,
bool perSampleShading)
{
// in highp int gl_SampleMaskIn[1]
const TVariable *glSampleMaskIn = static_cast<const TVariable *>(
symbolTable.findBuiltIn(ImmutableString("gl_SampleMaskIn"), compiler.getShaderVersion()));
DeclareRightBeforeMain(root, *glSampleMaskIn);
// Reference to gl_SampleMaskIn[0]
TIntermBinary *glSampleMaskIn0 =
new TIntermBinary(EOpIndexDirect, new TIntermSymbol(glSampleMaskIn), CreateIndexNode(0));
// When per-sample shading is active due to the use of a fragment input qualified
// by sample or due to the use of the gl_SampleID or gl_SamplePosition variables,
// only the bit for the current sample is set in gl_SampleMaskIn.
TIntermBlock *block = new TIntermBlock;
if (perSampleShading)
{
// gl_SampleMaskIn[0] = 1 << gl_SampleID;
block->appendStatement(new TIntermBinary(
EOpAssign, glSampleMaskIn0,
new TIntermBinary(EOpBitShiftLeft, CreateUIntNode(1),
new TIntermSymbol(BuiltInVariable::gl_SampleID()))));
}
else
{
// uint32_t ANGLE_metal_SampleMaskIn [[sample_mask]]
TVariable *angleSampleMaskIn = new TVariable(
&symbolTable, ImmutableString("metal_SampleMaskIn"),
new TType(EbtUInt, EbpHigh, EvqSampleMaskIn, 1), SymbolType::AngleInternal);
DeclareRightBeforeMain(root, *angleSampleMaskIn);
// gl_SampleMaskIn[0] = ANGLE_metal_SampleMaskIn;
block->appendStatement(
new TIntermBinary(EOpAssign, glSampleMaskIn0, new TIntermSymbol(angleSampleMaskIn)));
}
// Bits in the sample mask corresponding to covered samples
// that will be unset due to SAMPLE_COVERAGE or SAMPLE_MASK
// will not be set (section 4.1.3).
// if (ANGLEMultisampledRendering)
// {
// gl_SampleMaskIn[0] &= ANGLE_angleUniforms.coverageMask;
// }
TIntermBlock *coverageBlock = new TIntermBlock;
coverageBlock->appendStatement(new TIntermBinary(
EOpBitwiseAndAssign, glSampleMaskIn0->deepCopy(), driverUniforms->getCoverageMaskField()));
TVariable *sampleMaskEnabledVar = new TVariable(
&symbolTable, sh::ImmutableString(mtl::kMultisampledRenderingConstName),
StaticType::Get<EbtBool, EbpUndefined, EvqSpecConst, 1, 1>(), SymbolType::AngleInternal);
block->appendStatement(
new TIntermIfElse(new TIntermSymbol(sampleMaskEnabledVar), coverageBlock, nullptr));
return RunAtTheBeginningOfShader(&compiler, &root, block);
}
[[nodiscard]] bool AddSampleMaskDeclaration(TCompiler &compiler,
TIntermBlock &root,
TSymbolTable &symbolTable,
const DriverUniformMetal *driverUniforms,
bool includeEmulateAlphaToCoverage,
bool usesSampleMask)
{
// uint32_t ANGLE_metal_SampleMask [[sample_mask]]
TVariable *angleSampleMask =
new TVariable(&symbolTable, ImmutableString("metal_SampleMask"),
new TType(EbtUInt, EbpHigh, EvqSampleMask, 1), SymbolType::AngleInternal);
DeclareRightBeforeMain(root, *angleSampleMask);
// Write all-enabled sample mask even for single-sampled rendering
// when the shader uses derivatives to workaround a driver bug.
if (compiler.usesDerivatives())
{
TIntermBlock *helperAssignBlock = new TIntermBlock;
helperAssignBlock->appendStatement(new TIntermBinary(
EOpAssign, new TIntermSymbol(angleSampleMask), CreateUIntNode(0xFFFFFFFFu)));
TVariable *writeHelperSampleMaskVar =
new TVariable(&symbolTable, sh::ImmutableString(mtl::kWriteHelperSampleMaskConstName),
StaticType::Get<EbtBool, EbpUndefined, EvqSpecConst, 1, 1>(),
SymbolType::AngleInternal);
if (!RunAtTheBeginningOfShader(
&compiler, &root,
new TIntermIfElse(new TIntermSymbol(writeHelperSampleMaskVar), helperAssignBlock,
nullptr)))
{
return false;
}
}
// ANGLE_metal_SampleMask = ANGLE_angleUniforms.coverageMask;
TIntermBlock *block = new TIntermBlock;
block->appendStatement(new TIntermBinary(EOpAssign, new TIntermSymbol(angleSampleMask),
driverUniforms->getCoverageMaskField()));
if (usesSampleMask)
{
// out highp int gl_SampleMask[1];
const TVariable *glSampleMask = static_cast<const TVariable *>(
symbolTable.findBuiltIn(ImmutableString("gl_SampleMask"), compiler.getShaderVersion()));
DeclareRightBeforeMain(root, *glSampleMask);
// ANGLE_metal_SampleMask &= gl_SampleMask[0];
TIntermBinary *glSampleMask0 =
new TIntermBinary(EOpIndexDirect, new TIntermSymbol(glSampleMask), CreateIndexNode(0));
block->appendStatement(new TIntermBinary(
EOpBitwiseAndAssign, new TIntermSymbol(angleSampleMask), glSampleMask0));
}
if (includeEmulateAlphaToCoverage)
{
// Some Metal drivers ignore alpha-to-coverage state when a fragment
// shader writes to [[sample_mask]]. Moreover, Metal pipeline state
// does not support setting a global coverage mask, which would be used
// for emulating GL_SAMPLE_COVERAGE, so [[sample_mask]] is used instead.
// To support alpha-to-coverage regardless of the [[sample_mask]] usage,
// the former is always emulated on such drivers.
TIntermBlock *alphaBlock = new TIntermBlock;
// To reduce image artifacts due to regular coverage sample locations,
// alpha value thresholds that toggle individual samples are slightly
// different within 2x2 pixel blocks. Consider MSAAx4, for example.
// Instead of always enabling samples on evenly distributed alpha
// values like {51, 102, 153, 204} these thresholds may vary as follows
//
// Sample 0 Sample 1 Sample 2 Sample 3
// ----- ----- ----- ----- ----- ----- ----- -----
// | 7.5| 39.5| | 71.5|103.5| |135.5|167.5| |199.5|231.5|
// |----- -----| |----- -----| |----- -----| |----- -----|
// | 55.5| 23.5| |119.5| 87.5| |183.5|151.5| |247.5|215.5|
// ----- ----- ----- ----- ----- ----- ----- -----
// These threshold values may be expressed as
// 7.5 + P * 16 + 64 * sampleID
// where P is
// ((x << 1) - (y & 1)) & 3
// and constant values depend on the number of samples used.
TVariable *p = CreateTempVariable(&symbolTable, StaticType::GetBasic<EbtInt, EbpHigh>());
TVariable *y = CreateTempVariable(&symbolTable, StaticType::GetBasic<EbtInt, EbpHigh>());
alphaBlock->appendStatement(CreateTempInitDeclarationNode(
p, new TIntermSwizzle(new TIntermSymbol(BuiltInVariable::gl_FragCoord()), {0})));
alphaBlock->appendStatement(CreateTempInitDeclarationNode(
y, new TIntermSwizzle(new TIntermSymbol(BuiltInVariable::gl_FragCoord()), {1})));
alphaBlock->appendStatement(
new TIntermBinary(EOpBitShiftLeftAssign, new TIntermSymbol(p), CreateIndexNode(1)));
alphaBlock->appendStatement(
new TIntermBinary(EOpBitwiseAndAssign, new TIntermSymbol(y), CreateIndexNode(1)));
alphaBlock->appendStatement(
new TIntermBinary(EOpSubAssign, new TIntermSymbol(p), new TIntermSymbol(y)));
alphaBlock->appendStatement(
new TIntermBinary(EOpBitwiseAndAssign, new TIntermSymbol(p), CreateIndexNode(3)));
// This internal variable, defined in-text in the function constants section,
// will point to the alpha channel of the color zero output. Due to potential
// EXT_blend_func_extended usage, the exact variable may be unknown until the
// program is linked.
TVariable *alpha0 =
new TVariable(&symbolTable, sh::ImmutableString("_ALPHA0"),
StaticType::Get<EbtFloat, EbpUndefined, EvqSpecConst, 1, 1>(),
SymbolType::AngleInternal);
// Use metal::saturate to clamp the alpha value to [0.0, 1.0] and scale it
// to [0.0, 510.0] since further operations expect an integer alpha value.
TVariable *alphaScaled =
CreateTempVariable(&symbolTable, StaticType::GetBasic<EbtFloat, EbpHigh>());
alphaBlock->appendStatement(CreateTempInitDeclarationNode(
alphaScaled, CreateBuiltInFunctionCallNode("saturate", {new TIntermSymbol(alpha0)},
symbolTable, kESSLInternalBackendBuiltIns)));
alphaBlock->appendStatement(new TIntermBinary(EOpMulAssign, new TIntermSymbol(alphaScaled),
CreateFloatNode(510.0, EbpUndefined)));
// int alphaMask = int(alphaScaled);
TVariable *alphaMask =
CreateTempVariable(&symbolTable, StaticType::GetBasic<EbtInt, EbpHigh>());
alphaBlock->appendStatement(CreateTempInitDeclarationNode(
alphaMask, TIntermAggregate::CreateConstructor(*StaticType::GetBasic<EbtInt, EbpHigh>(),
{new TIntermSymbol(alphaScaled)})));
// Next operations depend on the number of samples in the curent render target.
TIntermBlock *switchBlock = new TIntermBlock();
auto computeNumberOfSamples = [&](int step, int bias, int scale) {
switchBlock->appendStatement(new TIntermBinary(
EOpBitShiftLeftAssign, new TIntermSymbol(p), CreateIndexNode(step)));
switchBlock->appendStatement(new TIntermBinary(
EOpAddAssign, new TIntermSymbol(alphaMask), CreateIndexNode(bias)));
switchBlock->appendStatement(new TIntermBinary(
EOpSubAssign, new TIntermSymbol(alphaMask), new TIntermSymbol(p)));
switchBlock->appendStatement(new TIntermBinary(
EOpBitShiftRightAssign, new TIntermSymbol(alphaMask), CreateIndexNode(scale)));
};
// MSAAx2
switchBlock->appendStatement(new TIntermCase(CreateIndexNode(2)));
// Canonical threshold values are
// 15.5 + P * 32 + 128 * sampleID
// With alpha values scaled to [0, 510], the number of covered samples is
// (alphaScaled + 256 - (31 + P * 64)) / 256
// which could be simplified to
// (alphaScaled + 225 - (P << 6)) >> 8
computeNumberOfSamples(6, 225, 8);
// In a case of only two samples, the coverage mask is
// mask = (num_covered_samples * 3) >> 1
switchBlock->appendStatement(
new TIntermBinary(EOpMulAssign, new TIntermSymbol(alphaMask), CreateIndexNode(3)));
switchBlock->appendStatement(new TIntermBinary(
EOpBitShiftRightAssign, new TIntermSymbol(alphaMask), CreateIndexNode(1)));
switchBlock->appendStatement(new TIntermBranch(EOpBreak, nullptr));
// MSAAx4
switchBlock->appendStatement(new TIntermCase(CreateIndexNode(4)));
// Canonical threshold values are
// 7.5 + P * 16 + 64 * sampleID
// With alpha values scaled to [0, 510], the number of covered samples is
// (alphaScaled + 128 - (15 + P * 32)) / 128
// which could be simplified to
// (alphaScaled + 113 - (P << 5)) >> 7
computeNumberOfSamples(5, 113, 7);
// When two out of four samples should be covered, prioritize
// those that are located in the opposite corners of a pixel.
// 0: 0000, 1: 0001, 2: 1001, 3: 1011, 4: 1111
// mask = (0xFB910 >> (num_covered_samples * 4)) & 0xF
// The final AND may be omitted because the rasterizer output
// is limited to four samples.
switchBlock->appendStatement(new TIntermBinary(
EOpBitShiftLeftAssign, new TIntermSymbol(alphaMask), CreateIndexNode(2)));
switchBlock->appendStatement(
new TIntermBinary(EOpAssign, new TIntermSymbol(alphaMask),
new TIntermBinary(EOpBitShiftRight, CreateIndexNode(0xFB910),
new TIntermSymbol(alphaMask))));
switchBlock->appendStatement(new TIntermBranch(EOpBreak, nullptr));
// MSAAx8
switchBlock->appendStatement(new TIntermCase(CreateIndexNode(8)));
// Canonical threshold values are
// 3.5 + P * 8 + 32 * sampleID
// With alpha values scaled to [0, 510], the number of covered samples is
// (alphaScaled + 64 - (7 + P * 16)) / 64
// which could be simplified to
// (alphaScaled + 57 - (P << 4)) >> 6
computeNumberOfSamples(4, 57, 6);
// When eight samples are used, they could be enabled one by one
// mask = ~(0xFFFFFFFF << num_covered_samples)
switchBlock->appendStatement(
new TIntermBinary(EOpAssign, new TIntermSymbol(alphaMask),
new TIntermBinary(EOpBitShiftLeft, CreateUIntNode(0xFFFFFFFFu),
new TIntermSymbol(alphaMask))));
switchBlock->appendStatement(new TIntermBinary(
EOpAssign, new TIntermSymbol(alphaMask),
new TIntermUnary(EOpBitwiseNot, new TIntermSymbol(alphaMask), nullptr)));
switchBlock->appendStatement(new TIntermBranch(EOpBreak, nullptr));
alphaBlock->getSequence()->push_back(
new TIntermSwitch(CreateBuiltInFunctionCallNode("numSamples", {}, symbolTable,
kESSLInternalBackendBuiltIns),
switchBlock));
alphaBlock->appendStatement(new TIntermBinary(
EOpBitwiseAndAssign, new TIntermSymbol(angleSampleMask), new TIntermSymbol(alphaMask)));
TIntermBlock *emulateAlphaToCoverageEnabledBlock = new TIntermBlock;
emulateAlphaToCoverageEnabledBlock->appendStatement(
new TIntermIfElse(driverUniforms->getAlphaToCoverage(), alphaBlock, nullptr));
TVariable *emulateAlphaToCoverageVar =
new TVariable(&symbolTable, sh::ImmutableString(mtl::kEmulateAlphaToCoverageConstName),
StaticType::Get<EbtBool, EbpUndefined, EvqSpecConst, 1, 1>(),
SymbolType::AngleInternal);
TIntermIfElse *useAlphaToCoverage =
new TIntermIfElse(new TIntermSymbol(emulateAlphaToCoverageVar),
emulateAlphaToCoverageEnabledBlock, nullptr);
block->appendStatement(useAlphaToCoverage);
}
// Sample mask assignment is guarded by ANGLEMultisampledRendering specialization constant
TVariable *multisampledRenderingVar = new TVariable(
&symbolTable, sh::ImmutableString(mtl::kMultisampledRenderingConstName),
StaticType::Get<EbtBool, EbpUndefined, EvqSpecConst, 1, 1>(), SymbolType::AngleInternal);
return RunAtTheEndOfShader(
&compiler, &root,
new TIntermIfElse(new TIntermSymbol(multisampledRenderingVar), block, nullptr),
&symbolTable);
}
[[nodiscard]] bool AddFragDataDeclaration(TCompiler &compiler,
TIntermBlock &root,
bool usesSecondary,
bool secondary)
{
TSymbolTable &symbolTable = compiler.getSymbolTable();
const int maxDrawBuffers = usesSecondary ? compiler.getResources().MaxDualSourceDrawBuffers
: compiler.getResources().MaxDrawBuffers;
TType *gl_FragDataType =
new TType(EbtFloat, EbpMedium, secondary ? EvqSecondaryFragDataEXT : EvqFragData, 4, 1);
std::vector<const TVariable *> glFragDataSlots;
TIntermSequence declareGLFragdataSequence;
// Create gl_FragData_i or gl_SecondaryFragDataEXT_i
const char *fragData = "gl_FragData";
const char *secondaryFragDataEXT = "gl_SecondaryFragDataEXT";
const char *name = secondary ? secondaryFragDataEXT : fragData;
for (int i = 0; i < maxDrawBuffers; i++)
{
ImmutableStringBuilder builder(strlen(name) + 3);
builder << name << "_";
builder.appendDecimal(i);
const TVariable *glFragData =
new TVariable(&symbolTable, builder, gl_FragDataType, SymbolType::AngleInternal,
TExtension::UNDEFINED);
glFragDataSlots.push_back(glFragData);
declareGLFragdataSequence.push_back(new TIntermDeclaration{glFragData});
}
root.insertChildNodes(FindMainIndex(&root), declareGLFragdataSequence);
// Create an internal gl_FragData array type, compatible with indexing syntax.
TType *gl_FragDataTypeArray = new TType(EbtFloat, EbpMedium, EvqGlobal, 4, 1);
gl_FragDataTypeArray->makeArray(maxDrawBuffers);
const TVariable *glFragDataGlobal = new TVariable(&symbolTable, ImmutableString(name),
gl_FragDataTypeArray, SymbolType::BuiltIn);
DeclareGlobalVariable(&root, glFragDataGlobal);
const TIntermSymbol *originalGLFragData = FindSymbolNode(&root, ImmutableString(name));
ASSERT(originalGLFragData);
// Replace gl_FragData[] or gl_SecondaryFragDataEXT[] with our globally defined variable
if (!ReplaceVariable(&compiler, &root, &(originalGLFragData->variable()), glFragDataGlobal))
{
return false;
}
// Assign each array attribute to an output
TIntermBlock *insertSequence = new TIntermBlock();
for (int i = 0; i < maxDrawBuffers; i++)
{
TIntermTyped *glFragDataSlot = new TIntermSymbol(glFragDataSlots[i]);
TIntermTyped *glFragDataGlobalSymbol = new TIntermSymbol(glFragDataGlobal);
auto &access = AccessIndex(*glFragDataGlobalSymbol, i);
TIntermBinary *assignment =
new TIntermBinary(TOperator::EOpAssign, glFragDataSlot, &access);
insertSequence->appendStatement(assignment);
}
return RunAtTheEndOfShader(&compiler, &root, insertSequence, &symbolTable);
}
[[nodiscard]] bool AppendVertexShaderTransformFeedbackOutputToMain(TCompiler &compiler,
SymbolEnv &mSymbolEnv,
TIntermBlock &root)
{
TSymbolTable &symbolTable = compiler.getSymbolTable();
// Append the assignment as a statement at the end of the shader.
return RunAtTheEndOfShader(&compiler, &root,
&(mSymbolEnv.callFunctionOverload(Name("@@XFB-OUT@@"), *new TType(),
*new TIntermSequence())),
&symbolTable);
}
// Unlike Vulkan having auto viewport flipping extension, in Metal we have to flip gl_Position.y
// manually.
// This operation performs flipping the gl_Position.y using this expression:
// gl_Position.y = gl_Position.y * negViewportScaleY
[[nodiscard]] bool AppendVertexShaderPositionYCorrectionToMain(TCompiler *compiler,
TIntermBlock *root,
TSymbolTable *symbolTable,
TIntermTyped *negFlipY)
{
// Create a symbol reference to "gl_Position"
const TVariable *position = BuiltInVariable::gl_Position();
TIntermSymbol *positionRef = new TIntermSymbol(position);
// Create a swizzle to "gl_Position.y"
TVector<int> swizzleOffsetY;
swizzleOffsetY.push_back(1);
TIntermSwizzle *positionY = new TIntermSwizzle(positionRef, swizzleOffsetY);
// Create the expression "gl_Position.y * negFlipY"
TIntermBinary *inverseY = new TIntermBinary(EOpMul, positionY->deepCopy(), negFlipY);
// Create the assignment "gl_Position.y = gl_Position.y * negViewportScaleY
TIntermTyped *positionYLHS = positionY->deepCopy();
TIntermBinary *assignment = new TIntermBinary(TOperator::EOpAssign, positionYLHS, inverseY);
// Append the assignment as a statement at the end of the shader.
return RunAtTheEndOfShader(compiler, root, assignment, symbolTable);
}
[[nodiscard]] bool EmulateClipDistanceVaryings(TCompiler *compiler,
TIntermBlock *root,
TSymbolTable *symbolTable,
const GLenum shaderType)
{
ASSERT(shaderType == GL_VERTEX_SHADER || shaderType == GL_FRAGMENT_SHADER);
const TIntermSymbol *symbolNode = FindSymbolNode(root, ImmutableString("gl_ClipDistance"));
ASSERT(symbolNode != nullptr);
const TVariable *clipDistanceVar = &symbolNode->variable();
const bool fragment = shaderType == GL_FRAGMENT_SHADER;
if (fragment)
{
TType *globalType = new TType(EbtFloat, EbpHigh, EvqGlobal, 1, 1);
globalType->toArrayBaseType();
globalType->makeArray(compiler->getClipDistanceArraySize());
const TVariable *globalVar = new TVariable(symbolTable, ImmutableString("ClipDistance"),
globalType, SymbolType::AngleInternal);
if (!compiler->isClipDistanceRedeclared())
{
TIntermDeclaration *globalDecl = new TIntermDeclaration();
globalDecl->appendDeclarator(new TIntermSymbol(globalVar));
root->insertStatement(0, globalDecl);
}
if (!ReplaceVariable(compiler, root, clipDistanceVar, globalVar))
{
return false;
}
clipDistanceVar = globalVar;
}
TIntermBlock *assignBlock = new TIntermBlock();
size_t index = FindMainIndex(root);
TIntermSymbol *arraySym = new TIntermSymbol(clipDistanceVar);
TType *type = new TType(EbtFloat, EbpHigh, fragment ? EvqFragmentIn : EvqVertexOut, 1, 1);
for (uint8_t i = 0; i < compiler->getClipDistanceArraySize(); i++)
{
std::stringstream name;
name << "ClipDistance_" << static_cast<int>(i);
TIntermSymbol *varyingSym = new TIntermSymbol(new TVariable(
symbolTable, ImmutableString(name.str()), type, SymbolType::AngleInternal));
TIntermDeclaration *varyingDecl = new TIntermDeclaration();
varyingDecl->appendDeclarator(varyingSym);
root->insertStatement(index++, varyingDecl);
TIntermTyped *arrayAccess = new TIntermBinary(EOpIndexDirect, arraySym, CreateIndexNode(i));
assignBlock->appendStatement(new TIntermBinary(
EOpAssign, fragment ? arrayAccess : varyingSym, fragment ? varyingSym : arrayAccess));
}
return fragment ? RunAtTheBeginningOfShader(compiler, root, assignBlock)
: RunAtTheEndOfShader(compiler, root, assignBlock, symbolTable);
}
} // namespace
namespace mtl
{
TranslatorMetalReflection *getTranslatorMetalReflection(const TCompiler *compiler)
{
return ((TranslatorMSL *)compiler)->getTranslatorMetalReflection();
}
} // namespace mtl
TranslatorMSL::TranslatorMSL(sh::GLenum type, ShShaderSpec spec, ShShaderOutput output)
: TCompiler(type, spec, output)
{}
[[nodiscard]] bool TranslatorMSL::insertRasterizationDiscardLogic(TIntermBlock &root)
{
// This transformation leaves the tree in an inconsistent state by using a variable that's
// defined in text, outside of the knowledge of the AST.
mValidateASTOptions.validateVariableReferences = false;
TSymbolTable *symbolTable = &getSymbolTable();
TType *boolType = new TType(EbtBool);
boolType->setQualifier(EvqConst);
TVariable *discardEnabledVar =
new TVariable(symbolTable, sh::ImmutableString(sh::mtl::kRasterizerDiscardEnabledConstName),
boolType, SymbolType::AngleInternal);
const TVariable *position = BuiltInVariable::gl_Position();
TIntermSymbol *positionRef = new TIntermSymbol(position);
// Create vec4(-3, -3, -3, 1):
auto vec4Type = new TType(EbtFloat, 4);
TIntermSequence vec4Args = {
CreateFloatNode(-3.0f, EbpMedium),
CreateFloatNode(-3.0f, EbpMedium),
CreateFloatNode(-3.0f, EbpMedium),
CreateFloatNode(1.0f, EbpMedium),
};
TIntermAggregate *constVarConstructor =
TIntermAggregate::CreateConstructor(*vec4Type, &vec4Args);
// Create the assignment "gl_Position = vec4(-3, -3, -3, 1)"
TIntermBinary *assignment =
new TIntermBinary(TOperator::EOpAssign, positionRef->deepCopy(), constVarConstructor);
TIntermBlock *discardBlock = new TIntermBlock;
discardBlock->appendStatement(assignment);
TIntermSymbol *discardEnabled = new TIntermSymbol(discardEnabledVar);
TIntermIfElse *ifCall = new TIntermIfElse(discardEnabled, discardBlock, nullptr);
return RunAtTheEndOfShader(this, &root, ifCall, symbolTable);
}
// Metal needs to inverse the depth if depthRange is is reverse order, i.e. depth near > depth far
// This is achieved by multiply the depth value with scale value stored in
// driver uniform's depthRange.reserved
bool TranslatorMSL::transformDepthBeforeCorrection(TIntermBlock *root,
const DriverUniformMetal *driverUniforms)
{
// Create a symbol reference to "gl_Position"
const TVariable *position = BuiltInVariable::gl_Position();
TIntermSymbol *positionRef = new TIntermSymbol(position);
// Create a swizzle to "gl_Position.z"
TVector<int> swizzleOffsetZ = {2};
TIntermSwizzle *positionZ = new TIntermSwizzle(positionRef, swizzleOffsetZ);
// Create a ref to "zscale"
TIntermTyped *viewportZScale = driverUniforms->getViewportZScale();
// Create the expression "gl_Position.z * zscale".
TIntermBinary *zScale = new TIntermBinary(EOpMul, positionZ->deepCopy(), viewportZScale);
// Create the assignment "gl_Position.z = gl_Position.z * zscale"
TIntermTyped *positionZLHS = positionZ->deepCopy();
TIntermBinary *assignment = new TIntermBinary(TOperator::EOpAssign, positionZLHS, zScale);
// Append the assignment as a statement at the end of the shader.
return RunAtTheEndOfShader(this, root, assignment, &getSymbolTable());
}
// This operation performs the viewport depth translation needed by Metal. GL uses a
// clip space z range of -1 to +1 where as Metal uses 0 to 1. The translation becomes
// this expression
//
// z_metal = 0.5 * (w_gl + z_gl)
//
// where z_metal is the depth output of a Metal vertex shader and z_gl is the same for GL.
// This operation is skipped when GL_CLIP_DEPTH_MODE_EXT is set to GL_ZERO_TO_ONE_EXT.
bool TranslatorMSL::appendVertexShaderDepthCorrectionToMain(
TIntermBlock *root,
const DriverUniformMetal *driverUniforms)
{
const TVariable *position = BuiltInVariable::gl_Position();
TIntermSymbol *positionRef = new TIntermSymbol(position);
TVector<int> swizzleOffsetZ = {2};
TIntermSwizzle *positionZ = new TIntermSwizzle(positionRef, swizzleOffsetZ);
TIntermConstantUnion *oneHalf = CreateFloatNode(0.5f, EbpMedium);
TVector<int> swizzleOffsetW = {3};
TIntermSwizzle *positionW = new TIntermSwizzle(positionRef->deepCopy(), swizzleOffsetW);
// Create the expression "(gl_Position.z + gl_Position.w) * 0.5".
TIntermBinary *zPlusW = new TIntermBinary(EOpAdd, positionZ->deepCopy(), positionW->deepCopy());
TIntermBinary *halfZPlusW = new TIntermBinary(EOpMul, zPlusW, oneHalf->deepCopy());
// Create the assignment "gl_Position.z = (gl_Position.z + gl_Position.w) * 0.5"
TIntermTyped *positionZLHS = positionZ->deepCopy();
TIntermBinary *assignment = new TIntermBinary(TOperator::EOpAssign, positionZLHS, halfZPlusW);
// Apply depth correction if needed
TIntermBlock *block = new TIntermBlock;
block->appendStatement(assignment);
TIntermIfElse *ifCall = new TIntermIfElse(driverUniforms->getTransformDepth(), block, nullptr);
// Append the assignment as a statement at the end of the shader.
return RunAtTheEndOfShader(this, root, ifCall, &getSymbolTable());
}
static inline MetalShaderType metalShaderTypeFromGLSL(sh::GLenum shaderType)
{
switch (shaderType)
{
case GL_VERTEX_SHADER:
return MetalShaderType::Vertex;
case GL_FRAGMENT_SHADER:
return MetalShaderType::Fragment;
case GL_COMPUTE_SHADER:
ASSERT(0 && "compute shaders not currently supported");
return MetalShaderType::Compute;
default:
ASSERT(0 && "Invalid shader type.");
return MetalShaderType::None;
}
}
bool TranslatorMSL::translateImpl(TInfoSinkBase &sink,
TIntermBlock *root,
const ShCompileOptions &compileOptions,
PerformanceDiagnostics * /*perfDiagnostics*/,
SpecConst *specConst,
DriverUniformMetal *driverUniforms)
{
TSymbolTable &symbolTable = getSymbolTable();
IdGen idGen;
ProgramPreludeConfig ppc(metalShaderTypeFromGLSL(getShaderType()));
ppc.usesDerivatives = usesDerivatives();
if (!WrapMain(*this, idGen, *root))
{
return false;
}
// 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(), false))
{
return false;
}
// Write out default uniforms into a uniform block assigned to a specific set/binding.
int aggregateTypesUsedForUniforms = 0;
int atomicCounterCount = 0;
for (const auto &uniform : getUniforms())
{
if (uniform.isStruct() || uniform.isArrayOfArrays())
{
++aggregateTypesUsedForUniforms;
}
if (uniform.active && gl::IsAtomicCounterType(uniform.type))
{
++atomicCounterCount;
}
}
// 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::SamplerCubeEmulation,
UnsupportedFunctionArgs::Image};
if (!MonomorphizeUnsupportedFunctions(this, root, &getSymbolTable(), compileOptions, args))
{
return false;
}
if (aggregateTypesUsedForUniforms > 0)
{
if (!NameEmbeddedStructUniformsMetal(this, root, &symbolTable))
{
return false;
}
if (!SeparateStructFromUniformDeclarations(this, root, &getSymbolTable()))
{
return false;
}
int removedUniformsCount;
if (!RewriteStructSamplers(this, root, &getSymbolTable(), &removedUniformsCount))
{
return false;
}
}
// 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 (compileOptions.emulateSeamfulCubeMapSampling)
{
if (!RewriteCubeMapSamplersAs2DArray(this, root, &symbolTable,
getShaderType() == GL_FRAGMENT_SHADER))
{
return false;
}
}
if (getShaderVersion() >= 300 ||
IsExtensionEnabled(getExtensionBehavior(), TExtension::EXT_shader_texture_lod))
{
if (compileOptions.preTransformTextureCubeGradDerivatives)
{
if (!PreTransformTextureCubeGradDerivatives(this, root, &symbolTable,
getShaderVersion()))
{
return false;
}
}
}
if (getShaderType() == GL_COMPUTE_SHADER)
{
driverUniforms->addComputeDriverUniformsToShader(root, &getSymbolTable());
}
else
{
driverUniforms->addGraphicsDriverUniformsToShader(root, &getSymbolTable());
}
if (atomicCounterCount > 0)
{
const TIntermTyped *acbBufferOffsets = driverUniforms->getAcbBufferOffsets();
if (!RewriteAtomicCounters(this, root, &symbolTable, acbBufferOffsets, nullptr))
{
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 (getShaderType() != GL_COMPUTE_SHADER)
{
if (!ReplaceGLDepthRangeWithDriverUniform(this, root, driverUniforms, &getSymbolTable()))
{
return false;
}
}
{
bool usesInstanceId = false;
bool usesVertexId = false;
for (const ShaderVariable &var : mAttributes)
{
if (var.isBuiltIn())
{
if (var.name == "gl_InstanceID")
{
usesInstanceId = true;
}
if (var.name == "gl_VertexID")
{
usesVertexId = true;
}
}
}
if (usesInstanceId)
{
root->insertChildNodes(
FindMainIndex(root),
TIntermSequence{new TIntermDeclaration{BuiltInVariable::gl_InstanceID()}});
}
if (usesVertexId)
{
if (!ReplaceVariable(this, root, BuiltInVariable::gl_VertexID(), &kgl_VertexIDMetal))
{
return false;
}
DeclareRightBeforeMain(*root, kgl_VertexIDMetal);
}
}
SymbolEnv symbolEnv(*this, *root);
bool usesSampleMask = false;
if (getShaderType() == GL_FRAGMENT_SHADER)
{
bool usesPointCoord = false;
bool usesFragCoord = false;
bool usesFrontFacing = false;
bool usesSampleID = false;
bool usesSamplePosition = false;
bool usesSampleMaskIn = false;
for (const ShaderVariable &inputVarying : mInputVaryings)
{
if (inputVarying.isBuiltIn())
{
if (inputVarying.name == "gl_PointCoord")
{
usesPointCoord = true;
}
else if (inputVarying.name == "gl_FragCoord")
{
usesFragCoord = true;
}
else if (inputVarying.name == "gl_FrontFacing")
{
usesFrontFacing = true;
}
else if (inputVarying.name == "gl_SampleID")
{
usesSampleID = true;
}
else if (inputVarying.name == "gl_SamplePosition")
{
usesSampleID = true;
usesSamplePosition = true;
}
else if (inputVarying.name == "gl_SampleMaskIn")
{
usesSampleMaskIn = true;
}
}
}
bool usesFragColor = false;
bool usesFragData = false;
bool usesFragDepth = false;
bool usesFragDepthEXT = false;
bool usesSecondaryFragColorEXT = false;
bool usesSecondaryFragDataEXT = false;
for (const ShaderVariable &outputVarying : mOutputVariables)
{
if (outputVarying.isBuiltIn())
{
if (outputVarying.name == "gl_FragColor")
{
usesFragColor = true;
}
else if (outputVarying.name == "gl_FragData")
{
usesFragData = true;
}
else if (outputVarying.name == "gl_FragDepth")
{
usesFragDepth = true;
}
else if (outputVarying.name == "gl_FragDepthEXT")
{
usesFragDepthEXT = true;
}
else if (outputVarying.name == "gl_SecondaryFragColorEXT")
{
usesSecondaryFragColorEXT = true;
}
else if (outputVarying.name == "gl_SecondaryFragDataEXT")
{
usesSecondaryFragDataEXT = true;
}
else if (outputVarying.name == "gl_SampleMask")
{
usesSampleMask = true;
}
}
}
// A shader may assign values to either the set of gl_FragColor and gl_SecondaryFragColorEXT
// or the set of gl_FragData and gl_SecondaryFragDataEXT, but not both.
ASSERT((!usesFragColor && !usesSecondaryFragColorEXT) ||
(!usesFragData && !usesSecondaryFragDataEXT));
if (usesFragColor)
{
AddFragColorDeclaration(*root, symbolTable, *BuiltInVariable::gl_FragColor());
}
else if (usesFragData)
{
if (!AddFragDataDeclaration(*this, *root, usesSecondaryFragDataEXT, false))
{
return false;
}
}
if (usesFragDepth)
{
AddFragDepthDeclaration(*root, symbolTable);
}
else if (usesFragDepthEXT)
{
AddFragDepthEXTDeclaration(*this, *root, symbolTable);
}
if (usesSecondaryFragColorEXT)
{
AddFragColorDeclaration(*root, symbolTable,
*BuiltInVariable::gl_SecondaryFragColorEXT());
}
else if (usesSecondaryFragDataEXT)
{
if (!AddFragDataDeclaration(*this, *root, usesSecondaryFragDataEXT, true))
{
return false;
}
}
bool usesSampleInterpolation = false;
bool usesSampleInterpolant = false;
if ((getShaderVersion() >= 320 ||
IsExtensionEnabled(getExtensionBehavior(),
TExtension::OES_shader_multisample_interpolation)) &&
!RewriteInterpolants(*this, *root, symbolTable, driverUniforms,
&usesSampleInterpolation, &usesSampleInterpolant))
{
return false;
}
if (usesSampleID || (usesSampleMaskIn && usesSampleInterpolation) || usesSampleInterpolant)
{
DeclareRightBeforeMain(*root, *BuiltInVariable::gl_SampleID());
}
if (usesSamplePosition)
{
if (!AddSamplePositionDeclaration(*this, *root, symbolTable, driverUniforms))
{
return false;
}
}
if (usesSampleMaskIn)
{
if (!AddSampleMaskInDeclaration(*this, *root, symbolTable, driverUniforms,
usesSampleID || usesSampleInterpolation))
{
return false;
}
}
if (usesPointCoord)
{
TIntermTyped *flipNegXY =
driverUniforms->getNegFlipXY(&getSymbolTable(), DriverUniformFlip::Fragment);
TIntermConstantUnion *pivot = CreateFloatNode(0.5f, EbpMedium);
if (!FlipBuiltinVariable(this, root, GetMainSequence(root), flipNegXY,
&getSymbolTable(), BuiltInVariable::gl_PointCoord(),
kFlippedPointCoordName, pivot))
{
return false;
}
DeclareRightBeforeMain(*root, *BuiltInVariable::gl_PointCoord());
}
if (usesFragCoord || compileOptions.emulateAlphaToCoverage ||
compileOptions.metal.generateShareableShaders)
{
if (!InsertFragCoordCorrection(this, compileOptions, root, GetMainSequence(root),
&getSymbolTable(), driverUniforms))
{
return false;
}
const TVariable *fragCoord = static_cast<const TVariable *>(
getSymbolTable().findBuiltIn(ImmutableString("gl_FragCoord"), getShaderVersion()));
DeclareRightBeforeMain(*root, *fragCoord);
}
if (!RewriteDfdy(this, root, &getSymbolTable(), getShaderVersion(), specConst,
driverUniforms))
{
return false;
}
if (getClipDistanceArraySize())
{
if (!EmulateClipDistanceVaryings(this, root, &getSymbolTable(), getShaderType()))
{
return false;
}
}
if (usesFrontFacing)
{
DeclareRightBeforeMain(*root, *BuiltInVariable::gl_FrontFacing());
}
bool usesNumSamples = false;
for (const ShaderVariable &uniform : mUniforms)
{
if (uniform.name == "gl_NumSamples")
{
usesNumSamples = true;
break;
}
}
if (usesNumSamples)
{
if (!AddNumSamplesDeclaration(*this, *root, symbolTable))
{
return false;
}
}
}
else if (getShaderType() == GL_VERTEX_SHADER)
{
DeclareRightBeforeMain(*root, *BuiltInVariable::gl_Position());
if (FindSymbolNode(root, BuiltInVariable::gl_PointSize()->name()))
{
const TVariable *pointSize = static_cast<const TVariable *>(
getSymbolTable().findBuiltIn(ImmutableString("gl_PointSize"), getShaderVersion()));
DeclareRightBeforeMain(*root, *pointSize);
}
if (FindSymbolNode(root, BuiltInVariable::gl_VertexIndex()->name()))
{
if (!ReplaceVariable(this, root, BuiltInVariable::gl_VertexIndex(), &kgl_VertexIDMetal))
{
return false;
}
DeclareRightBeforeMain(*root, kgl_VertexIDMetal);
}
// Append a macro for transform feedback substitution prior to modifying depth.
if (!AppendVertexShaderTransformFeedbackOutputToMain(*this, symbolEnv, *root))
{
return false;
}
if (getClipDistanceArraySize())
{
if (!ZeroDisabledClipDistanceAssignments(this, root, &getSymbolTable(), getShaderType(),
driverUniforms->getClipDistancesEnabled()))
{
return false;
}
if (IsExtensionEnabled(getExtensionBehavior(), TExtension::ANGLE_clip_cull_distance) &&
!EmulateClipDistanceVaryings(this, root, &getSymbolTable(), getShaderType()))
{
return false;
}
}
if (!transformDepthBeforeCorrection(root, driverUniforms))
{
return false;
}
if (!appendVertexShaderDepthCorrectionToMain(root, driverUniforms))
{
return false;
}
}
if (getShaderType() == GL_VERTEX_SHADER)
{
TIntermTyped *flipNegY =
driverUniforms->getFlipXY(&getSymbolTable(), DriverUniformFlip::PreFragment);
flipNegY = (new TIntermSwizzle(flipNegY, {1}))->fold(nullptr);
if (!AppendVertexShaderPositionYCorrectionToMain(this, root, &getSymbolTable(), flipNegY))
{
return false;
}
if (!insertRasterizationDiscardLogic(*root))
{
return false;
}
}
else if (getShaderType() == GL_FRAGMENT_SHADER)
{
mValidateASTOptions.validateVariableReferences = false;
if (!AddSampleMaskDeclaration(*this, *root, symbolTable, driverUniforms,
compileOptions.emulateAlphaToCoverage ||
compileOptions.metal.generateShareableShaders,
usesSampleMask))
{
return false;
}
}
if (!validateAST(root))
{
return false;
}
// This is the largest size required to pass all the tests in
// (dEQP-GLES3.functional.shaders.large_constant_arrays)
// This value could in principle be smaller.
const size_t hoistThresholdSize = 256;
if (!HoistConstants(*this, *root, idGen, hoistThresholdSize))
{
return false;
}
if (!ConvertUnsupportedConstructorsToFunctionCalls(*this, *root))
{
return false;
}
const bool needsExplicitBoolCasts = compileOptions.addExplicitBoolCasts;
if (!AddExplicitTypeCasts(*this, *root, symbolEnv, needsExplicitBoolCasts))
{
return false;
}
if (!SeparateCompoundStructDeclarations(*this, idGen, *root, &getSymbolTable()))
{
return false;
}
if (!SeparateCompoundExpressions(*this, symbolEnv, idGen, *root))
{
return false;
}
if (!ReduceInterfaceBlocks(*this, *root, idGen, &getSymbolTable()))
{
return false;
}
// The RewritePipelines phase leaves the tree in an inconsistent state by inserting
// references to structures like "ANGLE_TextureEnv<metal::texture2d<float>>" which are
// defined in text (in ProgramPrelude), outside of the knowledge of the AST.
mValidateASTOptions.validateStructUsage = false;
// The RewritePipelines phase also generates incoming arguments to synthesized
// functions that use are missing qualifiers - for example, angleUniforms isn't marked
// as an incoming argument.
mValidateASTOptions.validateQualifiers = false;
PipelineStructs pipelineStructs;
if (!RewritePipelines(*this, *root, getInputVaryings(), getOutputVaryings(), idGen,
*driverUniforms, symbolEnv, pipelineStructs))
{
return false;
}
if (getShaderType() == GL_VERTEX_SHADER)
{
// This has to happen after RewritePipelines.
if (!IntroduceVertexAndInstanceIndex(*this, *root))
{
return false;
}
}
if (!RewriteCaseDeclarations(*this, *root))
{
return false;
}
if (!RewriteUnaddressableReferences(*this, *root, symbolEnv))
{
return false;
}
if (!RewriteOutArgs(*this, *root, symbolEnv))
{
return false;
}
if (!FixTypeConstructors(*this, symbolEnv, *root))
{
return false;
}
if (!ToposortStructs(*this, symbolEnv, *root, ppc))
{
return false;
}
if (!EmitMetal(*this, *root, idGen, pipelineStructs, symbolEnv, ppc, compileOptions))
{
return false;
}
ASSERT(validateAST(root));
return true;
}
bool TranslatorMSL::translate(TIntermBlock *root,
const ShCompileOptions &compileOptions,
PerformanceDiagnostics *perfDiagnostics)
{
if (!root)
{
return false;
}
// TODO: refactor the code in TranslatorMSL to not issue raw function calls.
// http://anglebug.com/6059#c2
mValidateASTOptions.validateNoRawFunctionCalls = false;
// A validation error is generated in this backend due to bool uniforms.
mValidateASTOptions.validatePrecision = false;
TInfoSinkBase &sink = getInfoSink().obj;
SpecConst specConst(&getSymbolTable(), compileOptions, getShaderType());
DriverUniformMetal driverUniforms(DriverUniformMode::Structure);
if (!translateImpl(sink, root, compileOptions, perfDiagnostics, &specConst, &driverUniforms))
{
return false;
}
return true;
}
bool TranslatorMSL::shouldFlattenPragmaStdglInvariantAll()
{
// Not neccesary for MSL transformation.
return false;
}
} // namespace sh