Commit d8105a03 by Jiawei Shao Committed by Commit Bot

ES31: Implement gl_in in Geometry Shader

This patch intends to implement geometry shader built-in interface block instance gl_in defined in GL_OES_geometry_shader. 1. Add the definition of gl_in and its interface block gl_PerVertex into the symbol table. 2. Support gl_Position as a member of gl_in. 3. Set the array size of gl_in when a valid input primitive type is known. 4. Add check that it should be a compile error to index gl_in or call length() on gl_in without a valid input primitive declaration. This patch also adds unit tests to cover all these new features. BUG=angleproject:1941 TEST=angle_unittests Change-Id: I8da20c943b29c9ce904834625b396aab6302e1e1 Reviewed-on: https://chromium-review.googlesource.com/605059 Commit-Queue: Olli Etuaho <oetuaho@nvidia.com> Reviewed-by: 's avatarOlli Etuaho <oetuaho@nvidia.com>
parent 401345e4
......@@ -45,7 +45,11 @@ enum BlockLayoutType
enum class BlockType
{
BLOCK_UNIFORM,
BLOCK_BUFFER
BLOCK_BUFFER,
// Required in OpenGL ES 3.1 extension GL_OES_shader_io_blocks.
// TODO(jiawei.shao@intel.com): add BLOCK_OUT.
BLOCK_IN
};
// Base class for all variables defined in shaders, including Varyings, Uniforms, etc
......@@ -218,6 +222,8 @@ struct InterfaceBlock
// Decide whether two interface blocks are the same at shader link time.
bool isSameInterfaceBlockAtLinkTime(const InterfaceBlock &other) const;
bool isBuiltIn() const { return name.compare(0, 3, "gl_") == 0; }
std::string name;
std::string mappedName;
std::string instanceName;
......
......@@ -576,6 +576,7 @@ enum TQualifier
// GLSL ES 3.1 extension OES_geometry_shader qualifiers
EvqGeometryIn,
EvqGeometryOut,
EvqPerVertexIn,
EvqLayer, // gl_Layer
// end of list
......@@ -832,6 +833,7 @@ inline const char *getQualifierString(TQualifier q)
case EvqWriteOnly: return "writeonly";
case EvqGeometryIn: return "in";
case EvqGeometryOut: return "out";
case EvqPerVertexIn: return "gl_in";
default: UNREACHABLE(); return "unknown qualifier";
}
// clang-format on
......
......@@ -26,6 +26,7 @@ void CollectVariables(TIntermBlock *root,
std::vector<Varying> *outputVaryings,
std::vector<InterfaceBlock> *uniformBlocks,
std::vector<InterfaceBlock> *shaderStorageBlocks,
std::vector<InterfaceBlock> *inBlocks,
ShHashFunction64 hashFunction,
TSymbolTable *symbolTable,
int shaderVersion,
......
......@@ -475,8 +475,8 @@ TIntermBlock *TCompiler::compileTreeImpl(const char *const shaderStrings[],
{
ASSERT(!variablesCollected);
CollectVariables(root, &attributes, &outputVariables, &uniforms, &inputVaryings,
&outputVaryings, &uniformBlocks, &shaderStorageBlocks, hashFunction,
&symbolTable, shaderVersion, extensionBehavior);
&outputVaryings, &uniformBlocks, &shaderStorageBlocks, &inBlocks,
hashFunction, &symbolTable, shaderVersion, extensionBehavior);
collectInterfaceBlocks();
variablesCollected = true;
if (compileOptions & SH_USE_UNUSED_STANDARD_SHARED_BLOCKS)
......@@ -751,10 +751,11 @@ void TCompiler::setResourceString()
void TCompiler::collectInterfaceBlocks()
{
ASSERT(interfaceBlocks.empty());
interfaceBlocks.reserve(uniformBlocks.size() + shaderStorageBlocks.size());
interfaceBlocks.reserve(uniformBlocks.size() + shaderStorageBlocks.size() + inBlocks.size());
interfaceBlocks.insert(interfaceBlocks.end(), uniformBlocks.begin(), uniformBlocks.end());
interfaceBlocks.insert(interfaceBlocks.end(), shaderStorageBlocks.begin(),
shaderStorageBlocks.end());
interfaceBlocks.insert(interfaceBlocks.end(), inBlocks.begin(), inBlocks.end());
}
void TCompiler::clearResults()
......@@ -773,6 +774,7 @@ void TCompiler::clearResults()
interfaceBlocks.clear();
uniformBlocks.clear();
shaderStorageBlocks.clear();
inBlocks.clear();
variablesCollected = false;
mGLPositionInitialized = false;
......
......@@ -119,6 +119,7 @@ class TCompiler : public TShHandleBase
{
return shaderStorageBlocks;
}
const std::vector<sh::InterfaceBlock> &getInBlocks() const { return inBlocks; }
ShHashFunction64 getHashFunction() const { return hashFunction; }
NameMap &getNameMap() { return nameMap; }
......@@ -196,6 +197,7 @@ class TCompiler : public TShHandleBase
std::vector<sh::InterfaceBlock> interfaceBlocks;
std::vector<sh::InterfaceBlock> uniformBlocks;
std::vector<sh::InterfaceBlock> shaderStorageBlocks;
std::vector<sh::InterfaceBlock> inBlocks;
private:
// Creates the function call DAG for further analysis, returning false if there is a recursion
......
......@@ -908,9 +908,32 @@ void IdentifyBuiltIns(sh::GLenum type,
}
case GL_GEOMETRY_SHADER_OES:
// TODO(jiawei.shao@intel.com): add Geometry Shader built-in variables.
break;
{
// TODO(jiawei.shao@intel.com): add all Geometry Shader built-in variables.
const char *extension = "GL_OES_geometry_shader";
// Add built-in interface block gl_PerVertex and the built-in array gl_in.
// TODO(jiawei.shao@intel.com): implement GL_OES_geometry_point_size.
const TString *glPerVertexString = NewPoolTString("gl_PerVertex");
symbolTable.insertInterfaceBlockNameExt(ESSL3_1_BUILTINS, extension, glPerVertexString);
TFieldList *fieldList = NewPoolTFieldList();
TSourceLoc zeroSourceLoc = {0, 0, 0, 0};
TField *glPositionField = new TField(new TType(EbtFloat, EbpHigh, EvqPosition, 4),
NewPoolTString("gl_Position"), zeroSourceLoc);
fieldList->push_back(glPositionField);
TInterfaceBlock *glInBlock = new TInterfaceBlock(
glPerVertexString, fieldList, NewPoolTString("gl_in"), TLayoutQualifier::create());
// The array size of gl_in is undefined until we get a valid input primitive
// declaration.
TType glInType(glInBlock, EvqPerVertexIn, TLayoutQualifier::create(), 0);
glInType.setArrayUnsized();
symbolTable.insertVariableExt(ESSL3_1_BUILTINS, extension, "gl_in", glInType);
break;
}
default:
UNREACHABLE();
}
......
......@@ -622,7 +622,8 @@ bool TOutputGLSLBase::visitBinary(Visit visit, TIntermBinary *node)
const TField *field = interfaceBlock->fields()[index->getIConst(0)];
TString fieldName = field->name();
ASSERT(!mSymbolTable->findBuiltIn(interfaceBlock->name(), mShaderVersion));
ASSERT(!mSymbolTable->findBuiltIn(interfaceBlock->name(), mShaderVersion) ||
interfaceBlock->name() == "gl_PerVertex");
fieldName = hashName(TName(fieldName));
out << fieldName;
......
......@@ -95,6 +95,27 @@ bool CanSetDefaultPrecisionOnType(const TPublicType &type)
return true;
}
// Map input primitive types to input array sizes in a geometry shader.
GLuint GetGeometryShaderInputArraySize(TLayoutPrimitiveType primitiveType)
{
switch (primitiveType)
{
case EptPoints:
return 1u;
case EptLines:
return 2u;
case EptTriangles:
return 3u;
case EptLinesAdjacency:
return 4u;
case EptTrianglesAdjacency:
return 6u;
default:
UNREACHABLE();
return 0u;
}
}
} // namespace
// This tracks each binding point's current default offset for inheritance of subsequent
......@@ -184,7 +205,8 @@ TParseContext::TParseContext(TSymbolTable &symt,
mGeometryShaderInvocations(0),
mGeometryShaderMaxVertices(-1),
mMaxGeometryShaderInvocations(resources.MaxGeometryShaderInvocations),
mMaxGeometryShaderMaxVertices(resources.MaxGeometryOutputVertices)
mMaxGeometryShaderMaxVertices(resources.MaxGeometryOutputVertices),
mGeometryShaderInputArraySize(0)
{
mComputeShaderLocalSize.fill(-1);
}
......@@ -496,6 +518,9 @@ bool TParseContext::checkCanBeLValue(const TSourceLoc &line, const char *op, TIn
case EvqComputeIn:
message = "can't modify work group size variable";
break;
case EvqPerVertexIn:
message = "can't modify any member in gl_in";
break;
default:
//
// Type that can't be written to?
......@@ -1748,6 +1773,13 @@ TIntermTyped *TParseContext::parseVariableIdentifier(const TSourceLoc &location,
type.setQualifier(EvqConst);
node = new TIntermConstantUnion(constArray, type);
}
// TODO(jiawei.shao@intel.com): set array sizes for user-defined geometry shader inputs.
else if (variable->getType().getQualifier() == EvqPerVertexIn)
{
TType type(variable->getType());
type.setArraySize(mGeometryShaderInputArraySize);
node = new TIntermSymbol(variable->getUniqueId(), variable->getName(), type);
}
else
{
node = new TIntermSymbol(variable->getUniqueId(), variable->getName(), variable->getType());
......@@ -2683,6 +2715,15 @@ bool TParseContext::checkPrimitiveTypeMatchesTypeQualifier(const TTypeQualifier
}
}
void TParseContext::setGeometryShaderInputArraySizes()
{
// TODO(jiawei.shao@intel.com): check former input array sizes match the input primitive
// declaration.
ASSERT(mGeometryShaderInputArraySize == 0);
mGeometryShaderInputArraySize =
GetGeometryShaderInputArraySize(mGeometryShaderInputPrimitiveType);
}
bool TParseContext::parseGeometryShaderInputLayoutQualifier(const TTypeQualifier &typeQualifier)
{
ASSERT(typeQualifier.qualifier == EvqGeometryIn);
......@@ -2708,6 +2749,7 @@ bool TParseContext::parseGeometryShaderInputLayoutQualifier(const TTypeQualifier
if (mGeometryShaderInputPrimitiveType == EptUndefined)
{
mGeometryShaderInputPrimitiveType = layoutQualifier.primitiveType;
setGeometryShaderInputArraySizes();
}
else if (mGeometryShaderInputPrimitiveType != layoutQualifier.primitiveType)
{
......@@ -3337,6 +3379,7 @@ TIntermTyped *TParseContext::addConstructor(TIntermSequence *arguments,
//
// Interface/uniform blocks
// TODO(jiawei.shao@intel.com): implement GL_OES_shader_io_blocks.
//
TIntermDeclaration *TParseContext::addInterfaceBlock(
const TTypeQualifierBuilder &typeQualifierBuilder,
......@@ -3641,6 +3684,16 @@ TIntermTyped *TParseContext::addIndexExpression(TIntermTyped *baseExpression,
return CreateZeroNode(TType(EbtFloat, EbpHigh, EvqConst));
}
if (baseExpression->getQualifier() == EvqPerVertexIn)
{
ASSERT(mShaderType == GL_GEOMETRY_SHADER_OES);
if (mGeometryShaderInputPrimitiveType == EptUndefined)
{
error(location, "missing input primitive declaration before indexing gl_in.", "[");
return CreateZeroNode(TType(EbtFloat, EbpHigh, EvqConst));
}
}
TIntermConstantUnion *indexConstantUnion = indexExpression->getAsConstantUnion();
// TODO(oetuaho@nvidia.com): Get rid of indexConstantUnion == nullptr below once ANGLE is able
......@@ -3651,9 +3704,21 @@ TIntermTyped *TParseContext::addIndexExpression(TIntermTyped *baseExpression,
{
if (baseExpression->isInterfaceBlock())
{
error(location,
"array indexes for interface blocks arrays must be constant integral expressions",
"[");
// TODO(jiawei.shao@intel.com): implement GL_OES_shader_io_blocks.
switch (baseExpression->getQualifier())
{
case EvqPerVertexIn:
break;
case EvqUniform:
case EvqBuffer:
error(location,
"array indexes for uniform block arrays and shader storage block arrays "
"must be constant integral expressions",
"[");
break;
default:
UNREACHABLE();
}
}
else if (baseExpression->getQualifier() == EvqFragmentOut)
{
......@@ -5326,6 +5391,11 @@ TIntermTyped *TParseContext::addMethod(TFunction *fnCall,
{
error(loc, "length can only be called on arrays", "length");
}
else if (typedThis->getQualifier() == EvqPerVertexIn &&
mGeometryShaderInputPrimitiveType == EptUndefined)
{
error(loc, "missing input primitive declaration before calling length on gl_in", "length");
}
else
{
arraySize = typedThis->getArraySize();
......
......@@ -535,6 +535,7 @@ class TParseContext : angle::NonCopyable
bool checkPrimitiveTypeMatchesTypeQualifier(const TTypeQualifier &typeQualifier);
bool parseGeometryShaderInputLayoutQualifier(const TTypeQualifier &typeQualifier);
bool parseGeometryShaderOutputLayoutQualifier(const TTypeQualifier &typeQualifier);
void setGeometryShaderInputArraySizes();
// Set to true when the last/current declarator list was started with an empty declaration. The
// non-empty declaration error check will need to be performed if the empty declaration is
......@@ -600,6 +601,8 @@ class TParseContext : angle::NonCopyable
int mGeometryShaderMaxVertices;
int mMaxGeometryShaderInvocations;
int mMaxGeometryShaderMaxVertices;
int mGeometryShaderInputArraySize; // Track if all input array sizes are same and matches the
// latter input primitive declaration.
};
int PaParseStrings(size_t count,
......
......@@ -299,6 +299,18 @@ TInterfaceBlockName *TSymbolTable::declareInterfaceBlockName(const TString *name
return nullptr;
}
TInterfaceBlockName *TSymbolTable::insertInterfaceBlockNameExt(ESymbolLevel level,
const char *ext,
const TString *name)
{
TInterfaceBlockName *blockNameSymbol = new TInterfaceBlockName(this, name);
if (insert(level, ext, blockNameSymbol))
{
return blockNameSymbol;
}
return nullptr;
}
TVariable *TSymbolTable::insertVariable(ESymbolLevel level, const char *name, const TType &type)
{
return insertVariable(level, NewPoolTString(name), type);
......
......@@ -347,6 +347,9 @@ class TSymbolTable : angle::NonCopyable
const char *name,
const TType &type);
TVariable *insertStructType(ESymbolLevel level, TStructure *str);
TInterfaceBlockName *insertInterfaceBlockNameExt(ESymbolLevel level,
const char *ext,
const TString *name);
bool insertConstInt(ESymbolLevel level, const char *name, int value, TPrecision precision)
{
......
......@@ -253,7 +253,7 @@ class TType
TType(TInterfaceBlock *interfaceBlockIn,
TQualifier qualifierIn,
TLayoutQualifier layoutQualifierIn,
int arraySizeIn)
unsigned int arraySizeIn)
: type(EbtInterfaceBlock),
precision(EbpUndefined),
qualifier(qualifierIn),
......@@ -349,6 +349,7 @@ class TType
invalidateMangledName();
}
}
void setArrayUnsized() { setArraySize(0u); }
void clearArrayness()
{
if (array)
......
......@@ -131,6 +131,43 @@ class CollectFragmentVariablesTest : public CollectVariablesTest
CollectFragmentVariablesTest() : CollectVariablesTest(GL_FRAGMENT_SHADER) {}
};
class CollectGeometryVariablesTest : public CollectVariablesTest
{
public:
CollectGeometryVariablesTest() : CollectVariablesTest(GL_GEOMETRY_SHADER_OES) {}
protected:
void SetUp() override
{
ShBuiltInResources resources;
InitBuiltInResources(&resources);
resources.OES_geometry_shader = 1;
initTranslator(resources);
}
void initTranslator(const ShBuiltInResources &resources)
{
mTranslator.reset(
new TranslatorGLSL(mShaderType, SH_GLES3_1_SPEC, SH_GLSL_COMPATIBILITY_OUTPUT));
ASSERT_TRUE(mTranslator->Init(resources));
}
void compileGeometryShaderWithInputPrimitive(const std::string &inputPrimitive)
{
std::ostringstream sstream;
sstream << "#version 310 es\n"
<< "#extension GL_OES_geometry_shader : require\n"
<< "layout (" << inputPrimitive << ") in;\n"
<< "layout (points, max_vertices = 2) out;\n"
<< "void main()\n"
<< "{\n"
<< " vec4 value = gl_in[0].gl_Position;\n"
<< "}\n";
compile(sstream.str());
}
};
TEST_F(CollectFragmentVariablesTest, SimpleOutputVar)
{
const std::string &shaderString =
......@@ -830,3 +867,63 @@ TEST_F(CollectVertexVariablesTest, ViewID_OVR)
const Varying *varying = &varyings[0];
EXPECT_EQ("gl_Position", varying->name);
}
// Test all the fields of gl_in can be collected correctly in a geometry shader.
TEST_F(CollectGeometryVariablesTest, CollectGLInFields)
{
const std::string &shaderString =
"#version 310 es\n"
"#extension GL_OES_geometry_shader : require\n"
"layout (points) in;\n"
"layout (points, max_vertices = 2) out;\n"
"void main()\n"
"{\n"
" vec4 value = gl_in[0].gl_Position;\n"
" vec4 value2 = gl_in[0].gl_Position;\n"
"}\n";
compile(shaderString);
EXPECT_TRUE(mTranslator->getOutputVaryings().empty());
EXPECT_TRUE(mTranslator->getInputVaryings().empty());
const auto &inBlocks = mTranslator->getInBlocks();
ASSERT_EQ(1u, inBlocks.size());
const InterfaceBlock *inBlock = &inBlocks[0];
EXPECT_EQ("gl_PerVertex", inBlock->name);
EXPECT_EQ("gl_in", inBlock->instanceName);
EXPECT_TRUE(inBlock->staticUse);
EXPECT_TRUE(inBlock->isBuiltIn());
ASSERT_EQ(1u, inBlock->fields.size());
const ShaderVariable &glPositionField = inBlock->fields[0];
EXPECT_EQ("gl_Position", glPositionField.name);
EXPECT_FALSE(glPositionField.isArray());
EXPECT_FALSE(glPositionField.isStruct());
EXPECT_TRUE(glPositionField.staticUse);
EXPECT_TRUE(glPositionField.isBuiltIn());
EXPECT_GLENUM_EQ(GL_HIGH_FLOAT, glPositionField.precision);
EXPECT_GLENUM_EQ(GL_FLOAT_VEC4, glPositionField.type);
}
// Test the collected array size of gl_in matches the input primitive declaration.
TEST_F(CollectGeometryVariablesTest, GLInArraySize)
{
const std::array<std::string, 5> kInputPrimitives = {
{"points", "lines", "lines_adjacency", "triangles", "triangles_adjacency"}};
constexpr GLuint kArraySizeForInputPrimitives[] = {1u, 2u, 4u, 3u, 6u};
for (size_t i = 0; i < kInputPrimitives.size(); ++i)
{
compileGeometryShaderWithInputPrimitive(kInputPrimitives[i]);
const auto &inBlocks = mTranslator->getInBlocks();
ASSERT_EQ(1u, inBlocks.size());
const InterfaceBlock *inBlock = &inBlocks[0];
ASSERT_EQ("gl_in", inBlock->instanceName);
EXPECT_EQ(kArraySizeForInputPrimitives[i], inBlock->arraySize);
}
}
......@@ -869,3 +869,120 @@ TEST_F(GeometryShaderTest, invalidLayoutQualifiers)
FAIL() << "Shader compilation succeeded, expecting failure:\n" << mInfoLog;
}
}
// Verify that indexing an array with a constant integer on gl_in is legal.
TEST_F(GeometryShaderTest, IndexGLInByConstantInteger)
{
const std::string &shaderString =
"#version 310 es\n"
"#extension GL_OES_geometry_shader : require\n"
"layout (points) in;\n"
"layout (points, max_vertices = 2) out;\n"
"void main()\n"
"{\n"
" vec4 position;\n"
" position = gl_in[0].gl_Position;\n"
"}\n";
if (!compile(shaderString))
{
FAIL() << "Shader compilation failed, expecting success:\n" << mInfoLog;
}
}
// Verify that indexing an array with an integer variable on gl_in is legal.
TEST_F(GeometryShaderTest, IndexGLInByVariable)
{
const std::string &shaderString =
"#version 310 es\n"
"#extension GL_OES_geometry_shader : require\n"
"layout (lines) in;\n"
"layout (points, max_vertices = 2) out;\n"
"void main()\n"
"{\n"
" vec4 position;\n"
" for (int i = 0; i < 2; i++)\n"
" {\n"
" position = gl_in[i].gl_Position;\n"
" }\n"
"}\n";
if (!compile(shaderString))
{
FAIL() << "Shader compilation failed, expecting success:\n" << mInfoLog;
}
}
// Verify that indexing an array on gl_in without input primitive declaration causes a compile
// error.
TEST_F(GeometryShaderTest, IndexGLInWithoutInputPrimitive)
{
const std::string &shaderString =
"#version 310 es\n"
"#extension GL_OES_geometry_shader : require\n"
"layout (points, max_vertices = 2) out;\n"
"void main()\n"
"{\n"
" vec4 position = gl_in[0].gl_Position;\n"
"}\n";
if (compile(shaderString))
{
FAIL() << "Shader compilation succeeded, expecting failure:\n" << mInfoLog;
}
}
// Verify that using gl_in.length() without input primitive declaration causes a compile error.
TEST_F(GeometryShaderTest, UseGLInLengthWithoutInputPrimitive)
{
const std::string &shaderString =
"#version 310 es\n"
"#extension GL_OES_geometry_shader : require\n"
"layout (points, max_vertices = 2) out;\n"
"void main()\n"
"{\n"
" int length = gl_in.length();\n"
"}\n";
if (compile(shaderString))
{
FAIL() << "Shader compilation succeeded, expecting failure:\n" << mInfoLog;
}
}
// Verify that using gl_in.length() with input primitive declaration can compile.
TEST_F(GeometryShaderTest, UseGLInLengthWithInputPrimitive)
{
const std::string &shaderString =
"#version 310 es\n"
"#extension GL_OES_geometry_shader : require\n"
"layout (points) in;\n"
"layout (points, max_vertices = 2) out;\n"
"void main()\n"
"{\n"
" int length = gl_in.length();\n"
"}\n";
if (!compile(shaderString))
{
FAIL() << "Shader compilation failed, expecting success:\n" << mInfoLog;
}
}
// Verify that gl_in[].gl_Position cannot be l-value.
TEST_F(GeometryShaderTest, AssignValueToGLIn)
{
const std::string &shaderString =
"#version 310 es\n"
"#extension GL_OES_geometry_shader : require\n"
"layout (points) in;\n"
"layout (points, max_vertices = 2) out;\n"
"void main()\n"
"{\n"
" gl_in[0].gl_Position = vec4(0, 0, 0, 1);\n"
"}\n";
if (compile(shaderString))
{
FAIL() << "Shader compilation succeeded, expecting failure:\n" << mInfoLog;
}
}
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment