Flatten "#pragma STDGL invariant(all)" into varying variables.
This is implemented as a compiler option which is enabled by default
when outputting to desktop GLSL version 130 and greater, which does
not support this #pragma in fragment shaders. As a workaround, and for
better compatibility on desktop OpenGL drivers, this pragma is also
flattened into the outputs of vertex shaders, and the inputs of ESSL
1.00 fragment shaders.
TEST=conformance/glsl/misc/shaders-with-invariance.html with --enable-unsafe-es3-apis
BUG=629622, angleproject:1293
Change-Id: Ib040230915e639971505ed496d26e804c9d64e68
Reviewed-on: https://chromium-review.googlesource.com/361792
Reviewed-by: Jamie Madill <jmadill@chromium.org>
Reviewed-by: Yuly Novikov <ynovikov@chromium.org>
Commit-Queue: Kenneth Russell <kbr@chromium.org>
diff --git a/src/compiler/translator/Compiler.cpp b/src/compiler/translator/Compiler.cpp
index 55e74a1..06dc6cd 100644
--- a/src/compiler/translator/Compiler.cpp
+++ b/src/compiler/translator/Compiler.cpp
@@ -140,7 +140,8 @@
}
TCompiler::TCompiler(sh::GLenum type, ShShaderSpec spec, ShShaderOutput output)
- : shaderType(type),
+ : variablesCollected(false),
+ shaderType(type),
shaderSpec(spec),
outputType(output),
maxUniformVectors(0),
@@ -408,12 +409,20 @@
return NULL;
}
-bool TCompiler::compile(const char* const shaderStrings[],
- size_t numStrings, int compileOptions)
+bool TCompiler::compile(const char *const shaderStrings[], size_t numStrings, int compileOptionsIn)
{
if (numStrings == 0)
return true;
+ int compileOptions = compileOptionsIn;
+
+ // Apply key workarounds.
+ if (shouldFlattenPragmaStdglInvariantAll())
+ {
+ // This should be harmless to do in all cases, but for the moment, do it only conditionally.
+ compileOptions |= SH_FLATTEN_PRAGMA_STDGL_INVARIANT_ALL;
+ }
+
TScopedPoolAllocator scopedAlloc(&allocator);
TIntermNode *root = compileTreeImpl(shaderStrings, numStrings, compileOptions);
@@ -577,6 +586,7 @@
expandedUniforms.clear();
varyings.clear();
interfaceBlocks.clear();
+ variablesCollected = false;
builtInFunctionEmulator.Cleanup();
@@ -833,12 +843,16 @@
void TCompiler::collectVariables(TIntermNode* root)
{
- sh::CollectVariables collect(&attributes, &outputVariables, &uniforms, &varyings,
- &interfaceBlocks, hashFunction, symbolTable, extensionBehavior);
- root->traverse(&collect);
+ if (!variablesCollected)
+ {
+ sh::CollectVariables collect(&attributes, &outputVariables, &uniforms, &varyings,
+ &interfaceBlocks, hashFunction, symbolTable, extensionBehavior);
+ root->traverse(&collect);
- // This is for enforcePackingRestriction().
- sh::ExpandUniforms(uniforms, &expandedUniforms);
+ // This is for enforcePackingRestriction().
+ sh::ExpandUniforms(uniforms, &expandedUniforms);
+ variablesCollected = true;
+ }
}
bool TCompiler::enforcePackingRestrictions()
@@ -907,9 +921,26 @@
return builtInFunctionEmulator;
}
-void TCompiler::writePragma()
+void TCompiler::writePragma(int compileOptions)
{
- TInfoSinkBase &sink = infoSink.obj;
- if (mPragma.stdgl.invariantAll)
- sink << "#pragma STDGL invariant(all)\n";
+ if (!(compileOptions & SH_FLATTEN_PRAGMA_STDGL_INVARIANT_ALL))
+ {
+ TInfoSinkBase &sink = infoSink.obj;
+ if (mPragma.stdgl.invariantAll)
+ sink << "#pragma STDGL invariant(all)\n";
+ }
+}
+
+bool TCompiler::isVaryingDefined(const char *varyingName)
+{
+ ASSERT(variablesCollected);
+ for (size_t ii = 0; ii < varyings.size(); ++ii)
+ {
+ if (varyings[ii].name == varyingName)
+ {
+ return true;
+ }
+ }
+
+ return false;
}
diff --git a/src/compiler/translator/Compiler.h b/src/compiler/translator/Compiler.h
index b982556..db5ec26 100644
--- a/src/compiler/translator/Compiler.h
+++ b/src/compiler/translator/Compiler.h
@@ -154,24 +154,29 @@
const TExtensionBehavior& getExtensionBehavior() const;
const char *getSourcePath() const;
const TPragma& getPragma() const { return mPragma; }
- void writePragma();
+ void writePragma(int compileOptions);
unsigned int *getTemporaryIndex() { return &mTemporaryIndex; }
+ // Relies on collectVariables having been called.
+ bool isVaryingDefined(const char *varyingName);
const ArrayBoundsClamper& getArrayBoundsClamper() const;
ShArrayIndexClampingStrategy getArrayIndexClampingStrategy() const;
const BuiltInFunctionEmulator& getBuiltInFunctionEmulator() const;
+ virtual bool shouldCollectVariables(int compileOptions)
+ {
+ return (compileOptions & SH_VARIABLES) != 0;
+ }
+
+ virtual bool shouldFlattenPragmaStdglInvariantAll() = 0;
+
std::vector<sh::Attribute> attributes;
std::vector<sh::OutputVariable> outputVariables;
std::vector<sh::Uniform> uniforms;
std::vector<sh::ShaderVariable> expandedUniforms;
std::vector<sh::Varying> varyings;
std::vector<sh::InterfaceBlock> interfaceBlocks;
-
- virtual bool shouldCollectVariables(int compileOptions)
- {
- return (compileOptions & SH_VARIABLES) != 0;
- }
+ bool variablesCollected;
private:
// Creates the function call DAG for further analysis, returning false if there is a recursion
diff --git a/src/compiler/translator/ParseContext.cpp b/src/compiler/translator/ParseContext.cpp
index 2a5b60e..3476d88 100644
--- a/src/compiler/translator/ParseContext.cpp
+++ b/src/compiler/translator/ParseContext.cpp
@@ -1517,8 +1517,35 @@
const TSourceLoc &identifierOrTypeLocation,
const TString &identifier)
{
- TIntermSymbol *symbol =
- intermediate.addSymbol(0, identifier, TType(publicType), identifierOrTypeLocation);
+ TType type(publicType);
+ if ((mCompileOptions & SH_FLATTEN_PRAGMA_STDGL_INVARIANT_ALL) &&
+ mDirectiveHandler.pragma().stdgl.invariantAll)
+ {
+ TQualifier qualifier = type.getQualifier();
+
+ // The directive handler has already taken care of rejecting invalid uses of this pragma
+ // (for example, in ESSL 3.00 fragment shaders), so at this point, flatten it into all
+ // affected variable declarations:
+ //
+ // 1. Built-in special variables which are inputs to the fragment shader. (These are handled
+ // elsewhere, in TranslatorGLSL.)
+ //
+ // 2. Outputs from vertex shaders in ESSL 1.00 and 3.00 (EvqVaryingOut and EvqVertexOut). It
+ // is actually less likely that there will be bugs in the handling of ESSL 3.00 shaders, but
+ // the way this is currently implemented we have to enable this compiler option before
+ // parsing the shader and determining the shading language version it uses. If this were
+ // implemented as a post-pass, the workaround could be more targeted.
+ //
+ // 3. Inputs in ESSL 1.00 fragment shaders (EvqVaryingIn). This is somewhat in violation of
+ // the specification, but there are desktop OpenGL drivers that expect that this is the
+ // behavior of the #pragma when specified in ESSL 1.00 fragment shaders.
+ if (qualifier == EvqVaryingOut || qualifier == EvqVertexOut || qualifier == EvqVaryingIn)
+ {
+ type.setInvariant(true);
+ }
+ }
+
+ TIntermSymbol *symbol = intermediate.addSymbol(0, identifier, type, identifierOrTypeLocation);
bool emptyDeclaration = (identifier == "");
@@ -1541,7 +1568,7 @@
checkCanBeDeclaredWithoutInitializer(identifierOrTypeLocation, identifier, &publicType);
TVariable *variable = nullptr;
- declareVariable(identifierOrTypeLocation, identifier, TType(publicType), &variable);
+ declareVariable(identifierOrTypeLocation, identifier, type, &variable);
if (variable && symbol)
symbol->setId(variable->getUniqueId());
diff --git a/src/compiler/translator/ParseContext.h b/src/compiler/translator/ParseContext.h
index ccc7e21..0fb6e37 100644
--- a/src/compiler/translator/ParseContext.h
+++ b/src/compiler/translator/ParseContext.h
@@ -42,6 +42,7 @@
mDeferredSingleDeclarationErrorCheck(false),
mShaderType(type),
mShaderSpec(spec),
+ mCompileOptions(options),
mShaderVersion(100),
mTreeRoot(nullptr),
mLoopNestingLevel(0),
@@ -406,6 +407,7 @@
sh::GLenum mShaderType; // vertex or fragment language (future: pack or unpack)
ShShaderSpec mShaderSpec; // The language specification compiler conforms to - GLES2 or WebGL.
+ int mCompileOptions; // Options passed to TCompiler
int mShaderVersion;
TIntermNode *mTreeRoot; // root of parse tree being created
int mLoopNestingLevel; // 0 if outside all loops
diff --git a/src/compiler/translator/TranslatorESSL.cpp b/src/compiler/translator/TranslatorESSL.cpp
index c04b13d..392ea09 100644
--- a/src/compiler/translator/TranslatorESSL.cpp
+++ b/src/compiler/translator/TranslatorESSL.cpp
@@ -25,7 +25,8 @@
}
}
-void TranslatorESSL::translate(TIntermNode *root, int) {
+void TranslatorESSL::translate(TIntermNode *root, int compileOptions)
+{
TInfoSinkBase& sink = getInfoSink().obj;
int shaderVer = getShaderVersion();
@@ -39,7 +40,7 @@
// Write pragmas after extensions because some drivers consider pragmas
// like non-preprocessor tokens.
- writePragma();
+ writePragma(compileOptions);
bool precisionEmulation = getResources().WEBGL_debug_shader_precision && getPragma().debugShaderPrecision;
@@ -90,6 +91,12 @@
root->traverse(&outputESSL);
}
+bool TranslatorESSL::shouldFlattenPragmaStdglInvariantAll()
+{
+ // Not necessary when translating to ESSL.
+ return false;
+}
+
void TranslatorESSL::writeExtensionBehavior() {
TInfoSinkBase& sink = getInfoSink().obj;
const TExtensionBehavior& extBehavior = getExtensionBehavior();
diff --git a/src/compiler/translator/TranslatorESSL.h b/src/compiler/translator/TranslatorESSL.h
index 2cc6107..0fbc47d 100644
--- a/src/compiler/translator/TranslatorESSL.h
+++ b/src/compiler/translator/TranslatorESSL.h
@@ -18,6 +18,7 @@
void initBuiltInFunctionEmulator(BuiltInFunctionEmulator *emu, int compileOptions) override;
void translate(TIntermNode *root, int compileOptions) override;
+ bool shouldFlattenPragmaStdglInvariantAll() override;
private:
void writeExtensionBehavior();
diff --git a/src/compiler/translator/TranslatorGLSL.cpp b/src/compiler/translator/TranslatorGLSL.cpp
index 7174233..dc45410 100644
--- a/src/compiler/translator/TranslatorGLSL.cpp
+++ b/src/compiler/translator/TranslatorGLSL.cpp
@@ -42,7 +42,38 @@
// Write pragmas after extensions because some drivers consider pragmas
// like non-preprocessor tokens.
- writePragma();
+ writePragma(compileOptions);
+
+ // If flattening the global invariant pragma, write invariant declarations for built-in
+ // variables. It should be harmless to do this twice in the case that the shader also explicitly
+ // did this. However, it's important to emit invariant qualifiers only for those built-in
+ // variables that are actually used, to avoid affecting the behavior of the shader.
+ if ((compileOptions & SH_FLATTEN_PRAGMA_STDGL_INVARIANT_ALL) && getPragma().stdgl.invariantAll)
+ {
+ collectVariables(root);
+
+ switch (getShaderType())
+ {
+ case GL_VERTEX_SHADER:
+ sink << "invariant gl_Position;\n";
+
+ // gl_PointSize should be declared invariant in both ESSL 1.00 and 3.00 fragment
+ // shaders if it's statically referenced.
+ conditionallyOutputInvariantDeclaration("gl_PointSize");
+ break;
+ case GL_FRAGMENT_SHADER:
+ // The preprocessor will reject this pragma if it's used in ESSL 3.00 fragment
+ // shaders, so we can use simple logic to determine whether to declare these
+ // variables invariant.
+ conditionallyOutputInvariantDeclaration("gl_FragCoord");
+ conditionallyOutputInvariantDeclaration("gl_PointCoord");
+ break;
+ default:
+ // Currently not reached, but leave this in for future expansion.
+ ASSERT(false);
+ break;
+ }
+ }
bool precisionEmulation = getResources().WEBGL_debug_shader_precision && getPragma().debugShaderPrecision;
@@ -152,6 +183,13 @@
root->traverse(&outputGLSL);
}
+bool TranslatorGLSL::shouldFlattenPragmaStdglInvariantAll()
+{
+ // Required when outputting to any GLSL version greater than 1.20, but since ANGLE doesn't
+ // translate to that version, return true for the next higher version.
+ return IsGLSL130OrNewer(getOutputType());
+}
+
void TranslatorGLSL::writeVersion(TIntermNode *root)
{
TVersionGLSL versionGLSL(getShaderType(), getPragma(), getOutputType());
@@ -230,3 +268,12 @@
sink << "#extension " << ext << " : require\n";
}
}
+
+void TranslatorGLSL::conditionallyOutputInvariantDeclaration(const char *builtinVaryingName)
+{
+ if (isVaryingDefined(builtinVaryingName))
+ {
+ TInfoSinkBase &sink = getInfoSink().obj;
+ sink << "invariant " << builtinVaryingName << ";\n";
+ }
+}
diff --git a/src/compiler/translator/TranslatorGLSL.h b/src/compiler/translator/TranslatorGLSL.h
index 4f07b21..91df588 100644
--- a/src/compiler/translator/TranslatorGLSL.h
+++ b/src/compiler/translator/TranslatorGLSL.h
@@ -18,10 +18,12 @@
void initBuiltInFunctionEmulator(BuiltInFunctionEmulator *emu, int compileOptions) override;
void translate(TIntermNode *root, int compileOptions) override;
+ bool shouldFlattenPragmaStdglInvariantAll() override;
private:
void writeVersion(TIntermNode *root);
void writeExtensionBehavior(TIntermNode *root);
+ void conditionallyOutputInvariantDeclaration(const char *builtinVaryingName);
};
#endif // COMPILER_TRANSLATOR_TRANSLATORGLSL_H_
diff --git a/src/compiler/translator/TranslatorHLSL.cpp b/src/compiler/translator/TranslatorHLSL.cpp
index 15ab7af..7273da9 100644
--- a/src/compiler/translator/TranslatorHLSL.cpp
+++ b/src/compiler/translator/TranslatorHLSL.cpp
@@ -99,6 +99,12 @@
mUniformRegisterMap = outputHLSL.getUniformRegisterMap();
}
+bool TranslatorHLSL::shouldFlattenPragmaStdglInvariantAll()
+{
+ // Not necessary when translating to HLSL.
+ return false;
+}
+
bool TranslatorHLSL::hasInterfaceBlock(const std::string &interfaceBlockName) const
{
return (mInterfaceBlockRegisterMap.count(interfaceBlockName) > 0);
@@ -113,4 +119,4 @@
const std::map<std::string, unsigned int> *TranslatorHLSL::getUniformRegisterMap() const
{
return &mUniformRegisterMap;
-}
\ No newline at end of file
+}
diff --git a/src/compiler/translator/TranslatorHLSL.h b/src/compiler/translator/TranslatorHLSL.h
index 40cfd70..f96c18d 100644
--- a/src/compiler/translator/TranslatorHLSL.h
+++ b/src/compiler/translator/TranslatorHLSL.h
@@ -22,6 +22,7 @@
protected:
void translate(TIntermNode *root, int compileOptions) override;
+ bool shouldFlattenPragmaStdglInvariantAll() override;
// collectVariables needs to be run always so registers can be assigned.
bool shouldCollectVariables(int compileOptions) override { return true; }
diff --git a/src/compiler/translator/Types.h b/src/compiler/translator/Types.h
index 6295fd4..2a85e01 100644
--- a/src/compiler/translator/Types.h
+++ b/src/compiler/translator/Types.h
@@ -326,6 +326,8 @@
return invariant;
}
+ void setInvariant(bool i) { invariant = i; }
+
TLayoutQualifier getLayoutQualifier() const
{
return layoutQualifier;