blob: 21e7eda881fe495c820bfd202925f2fb0958d5b4 [file] [log] [blame]
//
// Copyright 2017 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.
//
// InitOutputVariables_test.cpp: Tests correctness of the AST pass enabled through
// SH_INIT_OUTPUT_VARIABLES.
//
#include "common/angleutils.h"
#include "compiler/translator/SymbolTable.h"
#include "compiler/translator/tree_util/FindMain.h"
#include "compiler/translator/tree_util/IntermNode_util.h"
#include "compiler/translator/tree_util/IntermTraverse.h"
#include "tests/test_utils/ShaderCompileTreeTest.h"
#include <algorithm>
namespace sh
{
namespace
{
typedef std::vector<TIntermTyped *> ExpectedLValues;
bool AreSymbolsTheSame(const TIntermSymbol *expected, const TIntermSymbol *candidate)
{
if (expected == nullptr || candidate == nullptr)
{
return false;
}
const TType &expectedType = expected->getType();
const TType &candidateType = candidate->getType();
const bool sameTypes = expectedType == candidateType &&
expectedType.getPrecision() == candidateType.getPrecision() &&
expectedType.getQualifier() == candidateType.getQualifier();
const bool sameSymbols = (expected->variable().symbolType() == SymbolType::Empty &&
candidate->variable().symbolType() == SymbolType::Empty) ||
expected->getName() == candidate->getName();
return sameSymbols && sameTypes;
}
bool AreLValuesTheSame(TIntermTyped *expected, TIntermTyped *candidate)
{
const TIntermBinary *expectedBinary = expected->getAsBinaryNode();
if (expectedBinary)
{
ASSERT(expectedBinary->getOp() == EOpIndexDirect);
const TIntermBinary *candidateBinary = candidate->getAsBinaryNode();
if (candidateBinary == nullptr || candidateBinary->getOp() != EOpIndexDirect)
{
return false;
}
if (expectedBinary->getRight()->getAsConstantUnion()->getIConst(0) !=
candidateBinary->getRight()->getAsConstantUnion()->getIConst(0))
{
return false;
}
return AreSymbolsTheSame(expectedBinary->getLeft()->getAsSymbolNode(),
candidateBinary->getLeft()->getAsSymbolNode());
}
return AreSymbolsTheSame(expected->getAsSymbolNode(), candidate->getAsSymbolNode());
}
TIntermTyped *CreateLValueNode(const ImmutableString &lValueName, const TType &type)
{
// We're using a dummy symbol table here, don't need to assign proper symbol ids to these nodes.
TSymbolTable symbolTable;
TVariable *variable =
new TVariable(&symbolTable, lValueName, new TType(type), SymbolType::UserDefined);
return new TIntermSymbol(variable);
}
ExpectedLValues CreateIndexedLValueNodeList(const ImmutableString &lValueName,
const TType &elementType,
unsigned arraySize)
{
ASSERT(elementType.isArray() == false);
TType *arrayType = new TType(elementType);
arrayType->makeArray(arraySize);
// We're using a dummy symbol table here, don't need to assign proper symbol ids to these nodes.
TSymbolTable symbolTable;
TVariable *variable =
new TVariable(&symbolTable, lValueName, arrayType, SymbolType::UserDefined);
TIntermSymbol *arraySymbol = new TIntermSymbol(variable);
ExpectedLValues expected(arraySize);
for (unsigned index = 0u; index < arraySize; ++index)
{
expected[index] = new TIntermBinary(EOpIndexDirect, arraySymbol->deepCopy(),
CreateIndexNode(static_cast<int>(index)));
}
return expected;
}
// VerifyOutputVariableInitializers traverses the subtree covering main and collects the lvalues in
// assignments for which the rvalue is an expression containing only zero constants.
class VerifyOutputVariableInitializers final : public TIntermTraverser
{
public:
VerifyOutputVariableInitializers(TIntermBlock *root) : TIntermTraverser(true, false, false)
{
ASSERT(root != nullptr);
// The traversal starts in the body of main because this is where the varyings and output
// variables are initialized.
sh::TIntermFunctionDefinition *main = FindMain(root);
ASSERT(main != nullptr);
main->traverse(this);
}
bool visitBinary(Visit visit, TIntermBinary *node) override
{
if (node->getOp() == EOpAssign && IsZero(node->getRight()))
{
mCandidateLValues.push_back(node->getLeft());
return false;
}
return true;
}
// The collected lvalues are considered valid if every expected lvalue in expectedLValues is
// matched by name and type with any lvalue in mCandidateLValues.
bool areAllExpectedLValuesFound(const ExpectedLValues &expectedLValues) const
{
for (size_t i = 0u; i < expectedLValues.size(); ++i)
{
if (!isExpectedLValueFound(expectedLValues[i]))
{
return false;
}
}
return true;
}
bool isExpectedLValueFound(TIntermTyped *expectedLValue) const
{
bool isFound = false;
for (size_t j = 0; j < mCandidateLValues.size() && !isFound; ++j)
{
isFound = AreLValuesTheSame(expectedLValue, mCandidateLValues[j]);
}
return isFound;
}
const ExpectedLValues &getCandidates() const { return mCandidateLValues; }
private:
ExpectedLValues mCandidateLValues;
};
// Traverses the AST and records a pointer to a structure with a given name.
class FindStructByName final : public TIntermTraverser
{
public:
FindStructByName(const ImmutableString &structName)
: TIntermTraverser(true, false, false), mStructName(structName), mStructure(nullptr)
{}
void visitSymbol(TIntermSymbol *symbol) override
{
if (isStructureFound())
{
return;
}
const TStructure *structure = symbol->getType().getStruct();
if (structure != nullptr && structure->symbolType() != SymbolType::Empty &&
structure->name() == mStructName)
{
mStructure = structure;
}
}
bool isStructureFound() const { return mStructure != nullptr; }
const TStructure *getStructure() const { return mStructure; }
private:
ImmutableString mStructName;
const TStructure *mStructure;
};
} // namespace
class InitOutputVariablesWebGL2Test : public ShaderCompileTreeTest
{
public:
void SetUp() override
{
mExtraCompileOptions |= SH_VARIABLES;
mExtraCompileOptions |= SH_INIT_OUTPUT_VARIABLES;
if (getShaderType() == GL_VERTEX_SHADER)
{
mExtraCompileOptions |= SH_INIT_GL_POSITION;
}
ShaderCompileTreeTest::SetUp();
}
protected:
ShShaderSpec getShaderSpec() const override { return SH_WEBGL2_SPEC; }
};
class InitOutputVariablesWebGL2VertexShaderTest : public InitOutputVariablesWebGL2Test
{
protected:
::GLenum getShaderType() const override { return GL_VERTEX_SHADER; }
};
class InitOutputVariablesWebGL2FragmentShaderTest : public InitOutputVariablesWebGL2Test
{
protected:
::GLenum getShaderType() const override { return GL_FRAGMENT_SHADER; }
void initResources(ShBuiltInResources *resources) override
{
resources->EXT_draw_buffers = 1;
resources->MaxDrawBuffers = 2;
}
};
class InitOutputVariablesWebGL1FragmentShaderTest : public ShaderCompileTreeTest
{
public:
InitOutputVariablesWebGL1FragmentShaderTest()
{
mExtraCompileOptions |= SH_VARIABLES;
mExtraCompileOptions |= SH_INIT_OUTPUT_VARIABLES;
}
protected:
::GLenum getShaderType() const override { return GL_FRAGMENT_SHADER; }
ShShaderSpec getShaderSpec() const override { return SH_WEBGL_SPEC; }
void initResources(ShBuiltInResources *resources) override
{
resources->EXT_draw_buffers = 1;
resources->MaxDrawBuffers = 2;
}
};
// Test the initialization of output variables with various qualifiers in a vertex shader.
TEST_F(InitOutputVariablesWebGL2VertexShaderTest, OutputAllQualifiers)
{
const std::string &shaderString =
"#version 300 es\n"
"precision mediump float;\n"
"precision lowp int;\n"
"out vec4 out1;\n"
"flat out int out2;\n"
"centroid out float out3;\n"
"smooth out float out4;\n"
"void main() {\n"
"}\n";
compileAssumeSuccess(shaderString);
VerifyOutputVariableInitializers verifier(mASTRoot);
ExpectedLValues expectedLValues = {
CreateLValueNode(ImmutableString("out1"), TType(EbtFloat, EbpMedium, EvqVertexOut, 4)),
CreateLValueNode(ImmutableString("out2"), TType(EbtInt, EbpLow, EvqFlatOut)),
CreateLValueNode(ImmutableString("out3"), TType(EbtFloat, EbpMedium, EvqCentroidOut)),
CreateLValueNode(ImmutableString("out4"), TType(EbtFloat, EbpMedium, EvqSmoothOut))};
EXPECT_TRUE(verifier.areAllExpectedLValuesFound(expectedLValues));
}
// Test the initialization of an output array in a vertex shader.
TEST_F(InitOutputVariablesWebGL2VertexShaderTest, OutputArray)
{
const std::string &shaderString =
"#version 300 es\n"
"precision mediump float;\n"
"out float out1[2];\n"
"void main() {\n"
"}\n";
compileAssumeSuccess(shaderString);
VerifyOutputVariableInitializers verifier(mASTRoot);
ExpectedLValues expectedLValues = CreateIndexedLValueNodeList(
ImmutableString("out1"), TType(EbtFloat, EbpMedium, EvqVertexOut), 2);
EXPECT_TRUE(verifier.areAllExpectedLValuesFound(expectedLValues));
}
// Test the initialization of a struct output variable in a vertex shader.
TEST_F(InitOutputVariablesWebGL2VertexShaderTest, OutputStruct)
{
const std::string &shaderString =
"#version 300 es\n"
"precision mediump float;\n"
"struct MyS{\n"
" float a;\n"
" float b;\n"
"};\n"
"out MyS out1;\n"
"void main() {\n"
"}\n";
compileAssumeSuccess(shaderString);
VerifyOutputVariableInitializers verifier(mASTRoot);
FindStructByName findStruct(ImmutableString("MyS"));
mASTRoot->traverse(&findStruct);
ASSERT(findStruct.isStructureFound());
TType type(findStruct.getStructure(), false);
type.setQualifier(EvqVertexOut);
TIntermTyped *expectedLValue = CreateLValueNode(ImmutableString("out1"), type);
EXPECT_TRUE(verifier.isExpectedLValueFound(expectedLValue));
delete expectedLValue;
}
// Test the initialization of a varying variable in an ESSL1 vertex shader.
TEST_F(InitOutputVariablesWebGL2VertexShaderTest, OutputFromESSL1Shader)
{
const std::string &shaderString =
"precision mediump float;\n"
"varying vec4 out1;\n"
"void main() {\n"
"}\n";
compileAssumeSuccess(shaderString);
VerifyOutputVariableInitializers verifier(mASTRoot);
TIntermTyped *expectedLValue =
CreateLValueNode(ImmutableString("out1"), TType(EbtFloat, EbpMedium, EvqVaryingOut, 4));
EXPECT_TRUE(verifier.isExpectedLValueFound(expectedLValue));
delete expectedLValue;
}
// Test the initialization of output variables in a fragment shader.
TEST_F(InitOutputVariablesWebGL2FragmentShaderTest, Output)
{
const std::string &shaderString =
"#version 300 es\n"
"precision mediump float;\n"
"out vec4 out1;\n"
"void main() {\n"
"}\n";
compileAssumeSuccess(shaderString);
VerifyOutputVariableInitializers verifier(mASTRoot);
TIntermTyped *expectedLValue =
CreateLValueNode(ImmutableString("out1"), TType(EbtFloat, EbpMedium, EvqFragmentOut, 4));
EXPECT_TRUE(verifier.isExpectedLValueFound(expectedLValue));
delete expectedLValue;
}
// Test the initialization of gl_FragData in a WebGL2 ESSL1 fragment shader. Only writes to
// gl_FragData[0] should be found.
TEST_F(InitOutputVariablesWebGL2FragmentShaderTest, FragData)
{
const std::string &shaderString =
"precision mediump float;\n"
"void main() {\n"
" gl_FragData[0] = vec4(1.);\n"
"}\n";
compileAssumeSuccess(shaderString);
VerifyOutputVariableInitializers verifier(mASTRoot);
ExpectedLValues expectedLValues = CreateIndexedLValueNodeList(
ImmutableString("gl_FragData"), TType(EbtFloat, EbpMedium, EvqFragData, 4), 1);
EXPECT_TRUE(verifier.isExpectedLValueFound(expectedLValues[0]));
EXPECT_EQ(1u, verifier.getCandidates().size());
}
// Test the initialization of gl_FragData in a WebGL1 ESSL1 fragment shader. Only writes to
// gl_FragData[0] should be found.
TEST_F(InitOutputVariablesWebGL1FragmentShaderTest, FragData)
{
const std::string &shaderString =
"precision mediump float;\n"
"void main() {\n"
" gl_FragData[0] = vec4(1.);\n"
"}\n";
compileAssumeSuccess(shaderString);
VerifyOutputVariableInitializers verifier(mASTRoot);
// In the symbol table, gl_FragData array has 2 elements. However, only the 1st one should be
// initialized.
ExpectedLValues expectedLValues = CreateIndexedLValueNodeList(
ImmutableString("gl_FragData"), TType(EbtFloat, EbpMedium, EvqFragData, 4), 2);
EXPECT_TRUE(verifier.isExpectedLValueFound(expectedLValues[0]));
EXPECT_EQ(1u, verifier.getCandidates().size());
}
// Test the initialization of gl_FragData in a WebGL1 ESSL1 fragment shader with GL_EXT_draw_buffers
// enabled. All attachment slots should be initialized.
TEST_F(InitOutputVariablesWebGL1FragmentShaderTest, FragDataWithDrawBuffersExtEnabled)
{
const std::string &shaderString =
"#extension GL_EXT_draw_buffers : enable\n"
"precision mediump float;\n"
"void main() {\n"
" gl_FragData[0] = vec4(1.);\n"
"}\n";
compileAssumeSuccess(shaderString);
VerifyOutputVariableInitializers verifier(mASTRoot);
ExpectedLValues expectedLValues = CreateIndexedLValueNodeList(
ImmutableString("gl_FragData"), TType(EbtFloat, EbpMedium, EvqFragData, 4), 2);
EXPECT_TRUE(verifier.isExpectedLValueFound(expectedLValues[0]));
EXPECT_TRUE(verifier.isExpectedLValueFound(expectedLValues[1]));
EXPECT_EQ(2u, verifier.getCandidates().size());
}
// Test that gl_Position is initialized once in case it is not statically used and both
// SH_INIT_OUTPUT_VARIABLES and SH_INIT_GL_POSITION flags are set.
TEST_F(InitOutputVariablesWebGL2VertexShaderTest, InitGLPositionWhenNotStaticallyUsed)
{
const std::string &shaderString =
"#version 300 es\n"
"precision highp float;\n"
"void main() {\n"
"}\n";
compileAssumeSuccess(shaderString);
VerifyOutputVariableInitializers verifier(mASTRoot);
TIntermTyped *glPosition =
CreateLValueNode(ImmutableString("gl_Position"), TType(EbtFloat, EbpHigh, EvqPosition, 4));
EXPECT_TRUE(verifier.isExpectedLValueFound(glPosition));
EXPECT_EQ(1u, verifier.getCandidates().size());
}
// Test that gl_Position is initialized once in case it is statically used and both
// SH_INIT_OUTPUT_VARIABLES and SH_INIT_GL_POSITION flags are set.
TEST_F(InitOutputVariablesWebGL2VertexShaderTest, InitGLPositionOnceWhenStaticallyUsed)
{
const std::string &shaderString =
"#version 300 es\n"
"precision highp float;\n"
"void main() {\n"
" gl_Position = vec4(1.0);\n"
"}\n";
compileAssumeSuccess(shaderString);
VerifyOutputVariableInitializers verifier(mASTRoot);
TIntermTyped *glPosition =
CreateLValueNode(ImmutableString("gl_Position"), TType(EbtFloat, EbpHigh, EvqPosition, 4));
EXPECT_TRUE(verifier.isExpectedLValueFound(glPosition));
EXPECT_EQ(1u, verifier.getCandidates().size());
}
} // namespace sh