Commit 3d932d83 by Olli Etuaho Committed by Commit Bot

Defer global initializers when necessary

Move global variable initializers that are not constant expressions to a function that gets called at the start of main(). This is done with an AST transformation. This needs to be done because global variable initializers must be constant in native GL, but ANGLE is more lenient with what can be put into ESSL 1.00 global initializers to remain compatible with legacy WebGL content. Non-constant global variable initializers also caused issues in HLSL output, since in HLSL output some types of expressions get unfolded into multiple statements. These include short-circuiting operators and array initialization. To make sure that these cases are covered, any initializers that can't be constant folded are deferred, even if they have the const qualifier. The old deferring mechanism in OutputHLSL is removed in favor of this new AST transformation based approach. BUG=angleproject:819 BUG=angleproject:1205 BUG=angleproject:1350 BUG=596616 TEST=WebGL conformance test conformance/glsl/misc/global-variable-init.html Change-Id: I039cc05d6b8c284baeefbdf7f10062cae4bc5716 Reviewed-on: https://chromium-review.googlesource.com/338291Reviewed-by: 's avatarCorentin Wallez <cwallez@chromium.org> Commit-Queue: Olli Etuaho <oetuaho@nvidia.com>
parent b079c7af
...@@ -34,6 +34,8 @@ ...@@ -34,6 +34,8 @@
'compiler/translator/Compiler.cpp', 'compiler/translator/Compiler.cpp',
'compiler/translator/Compiler.h', 'compiler/translator/Compiler.h',
'compiler/translator/ConstantUnion.h', 'compiler/translator/ConstantUnion.h',
'compiler/translator/DeferGlobalInitializers.cpp',
'compiler/translator/DeferGlobalInitializers.h',
'compiler/translator/Diagnostics.cpp', 'compiler/translator/Diagnostics.cpp',
'compiler/translator/Diagnostics.h', 'compiler/translator/Diagnostics.h',
'compiler/translator/DirectiveHandler.cpp', 'compiler/translator/DirectiveHandler.cpp',
......
...@@ -7,6 +7,7 @@ ...@@ -7,6 +7,7 @@
#include "compiler/translator/Cache.h" #include "compiler/translator/Cache.h"
#include "compiler/translator/Compiler.h" #include "compiler/translator/Compiler.h"
#include "compiler/translator/CallDAG.h" #include "compiler/translator/CallDAG.h"
#include "compiler/translator/DeferGlobalInitializers.h"
#include "compiler/translator/ForLoopUnroll.h" #include "compiler/translator/ForLoopUnroll.h"
#include "compiler/translator/Initialize.h" #include "compiler/translator/Initialize.h"
#include "compiler/translator/InitializeParseContext.h" #include "compiler/translator/InitializeParseContext.h"
...@@ -376,6 +377,11 @@ TIntermNode *TCompiler::compileTreeImpl(const char *const shaderStrings[], ...@@ -376,6 +377,11 @@ TIntermNode *TCompiler::compileTreeImpl(const char *const shaderStrings[],
RegenerateStructNames gen(symbolTable, shaderVersion); RegenerateStructNames gen(symbolTable, shaderVersion);
root->traverse(&gen); root->traverse(&gen);
} }
if (success)
{
DeferGlobalInitializers(root);
}
} }
SetGlobalParseContext(NULL); SetGlobalParseContext(NULL);
......
//
// Copyright (c) 2016 The ANGLE Project Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
//
// DeferGlobalInitializers is an AST traverser that moves global initializers into a function, and
// adds a function call to that function in the beginning of main().
// This enables initialization of globals with uniforms or non-constant globals, as allowed by
// the WebGL spec. Some initializers referencing non-constants may need to be unfolded into if
// statements in HLSL - this kind of steps should be done after DeferGlobalInitializers is run.
//
#include "compiler/translator/DeferGlobalInitializers.h"
#include "compiler/translator/IntermNode.h"
#include "compiler/translator/SymbolTable.h"
namespace
{
void SetInternalFunctionName(TIntermAggregate *functionNode, const char *name)
{
TString nameStr(name);
nameStr = TFunction::mangleName(nameStr);
TName nameObj(nameStr);
nameObj.setInternal(true);
functionNode->setNameObj(nameObj);
}
TIntermAggregate *CreateFunctionPrototypeNode(const char *name)
{
TIntermAggregate *functionNode = new TIntermAggregate(EOpPrototype);
SetInternalFunctionName(functionNode, name);
TType returnType(EbtVoid);
functionNode->setType(returnType);
return functionNode;
}
TIntermAggregate *CreateFunctionDefinitionNode(const char *name, TIntermAggregate *functionBody)
{
TIntermAggregate *functionNode = new TIntermAggregate(EOpFunction);
TIntermAggregate *paramsNode = new TIntermAggregate(EOpParameters);
functionNode->getSequence()->push_back(paramsNode);
functionNode->getSequence()->push_back(functionBody);
SetInternalFunctionName(functionNode, name);
TType returnType(EbtVoid);
functionNode->setType(returnType);
return functionNode;
}
TIntermAggregate *CreateFunctionCallNode(const char *name)
{
TIntermAggregate *functionNode = new TIntermAggregate(EOpFunctionCall);
SetInternalFunctionName(functionNode, name);
TType returnType(EbtVoid);
functionNode->setType(returnType);
return functionNode;
}
class DeferGlobalInitializersTraverser : public TIntermTraverser
{
public:
DeferGlobalInitializersTraverser();
bool visitBinary(Visit visit, TIntermBinary *node) override;
void insertInitFunction(TIntermNode *root);
private:
TIntermSequence mDeferredInitializers;
};
DeferGlobalInitializersTraverser::DeferGlobalInitializersTraverser()
: TIntermTraverser(true, false, false)
{
}
bool DeferGlobalInitializersTraverser::visitBinary(Visit visit, TIntermBinary *node)
{
if (node->getOp() == EOpInitialize)
{
TIntermSymbol *symbolNode = node->getLeft()->getAsSymbolNode();
ASSERT(symbolNode);
TIntermTyped *expression = node->getRight();
if (symbolNode->getQualifier() == EvqGlobal &&
(expression->getQualifier() != EvqConst || expression->getAsConstantUnion() == nullptr))
{
// For variables which are not constant, defer their real initialization until
// after we initialize uniforms.
// Deferral is done also in any cases where the variable has not been constant folded,
// since otherwise there's a chance that HLSL output will generate extra statements
// from the initializer expression.
TIntermBinary *deferredInit = new TIntermBinary(EOpAssign);
deferredInit->setLeft(node->getLeft()->deepCopy());
deferredInit->setRight(node->getRight());
deferredInit->setType(node->getType());
mDeferredInitializers.push_back(deferredInit);
// Remove the initializer from the global scope and just declare the global instead.
mReplacements.push_back(NodeUpdateEntry(getParentNode(), node, node->getLeft(), false));
}
}
return false;
}
void DeferGlobalInitializersTraverser::insertInitFunction(TIntermNode *root)
{
if (mDeferredInitializers.empty())
{
return;
}
TIntermAggregate *rootAgg = root->getAsAggregate();
ASSERT(rootAgg != nullptr && rootAgg->getOp() == EOpSequence);
const char *functionName = "initializeDeferredGlobals";
// Add function prototype to the beginning of the shader
TIntermAggregate *functionPrototypeNode = CreateFunctionPrototypeNode(functionName);
rootAgg->getSequence()->insert(rootAgg->getSequence()->begin(), functionPrototypeNode);
// Add function definition to the end of the shader
TIntermAggregate *functionBodyNode = new TIntermAggregate(EOpSequence);
TIntermSequence *functionBody = functionBodyNode->getSequence();
for (const auto &deferredInit : mDeferredInitializers)
{
functionBody->push_back(deferredInit);
}
TIntermAggregate *functionDefinition =
CreateFunctionDefinitionNode(functionName, functionBodyNode);
rootAgg->getSequence()->push_back(functionDefinition);
// Insert call into main function
for (TIntermNode *node : *rootAgg->getSequence())
{
TIntermAggregate *nodeAgg = node->getAsAggregate();
if (nodeAgg != nullptr && nodeAgg->getOp() == EOpFunction &&
TFunction::unmangleName(nodeAgg->getName()) == "main")
{
TIntermAggregate *functionCallNode = CreateFunctionCallNode(functionName);
TIntermNode *mainBody = nodeAgg->getSequence()->back();
TIntermAggregate *mainBodyAgg = mainBody->getAsAggregate();
ASSERT(mainBodyAgg != nullptr && mainBodyAgg->getOp() == EOpSequence);
mainBodyAgg->getSequence()->insert(mainBodyAgg->getSequence()->begin(),
functionCallNode);
}
}
}
} // namespace
void DeferGlobalInitializers(TIntermNode *root)
{
DeferGlobalInitializersTraverser traverser;
root->traverse(&traverser);
// Replace the initializers of the global variables.
traverser.updateTree();
// Add the function with initialization and the call to that.
traverser.insertInitFunction(root);
}
//
// Copyright (c) 2016 The ANGLE Project Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
//
// DeferGlobalInitializers is an AST traverser that moves global initializers into a function, and
// adds a function call to that function in the beginning of main().
// This enables initialization of globals with uniforms or non-constant globals, as allowed by
// the WebGL spec. Some initializers referencing non-constants may need to be unfolded into if
// statements in HLSL - this kind of steps should be done after DeferGlobalInitializers is run.
//
#ifndef COMPILER_TRANSLATOR_DEFERGLOBALINITIALIZERS_H_
#define COMPILER_TRANSLATOR_DEFERGLOBALINITIALIZERS_H_
class TIntermNode;
void DeferGlobalInitializers(TIntermNode *root);
#endif // COMPILER_TRANSLATOR_DEFERGLOBALINITIALIZERS_H_
...@@ -259,10 +259,6 @@ void OutputHLSL::output(TIntermNode *treeRoot, TInfoSinkBase &objSink) ...@@ -259,10 +259,6 @@ void OutputHLSL::output(TIntermNode *treeRoot, TInfoSinkBase &objSink)
mInfoSinkStack.pop(); mInfoSinkStack.pop();
mInfoSinkStack.push(&mFooter); mInfoSinkStack.push(&mFooter);
if (!mDeferredGlobalInitializers.empty())
{
writeDeferredGlobalInitializers(mFooter);
}
mInfoSinkStack.pop(); mInfoSinkStack.pop();
mInfoSinkStack.push(&mHeader); mInfoSinkStack.push(&mHeader);
...@@ -1784,20 +1780,11 @@ bool OutputHLSL::visitBinary(Visit visit, TIntermBinary *node) ...@@ -1784,20 +1780,11 @@ bool OutputHLSL::visitBinary(Visit visit, TIntermBinary *node)
ASSERT(symbolNode); ASSERT(symbolNode);
TIntermTyped *expression = node->getRight(); TIntermTyped *expression = node->getRight();
// TODO (jmadill): do a 'deep' scan to know if an expression is statically const // Global initializers must be constant at this point.
if (symbolNode->getQualifier() == EvqGlobal && expression->getQualifier() != EvqConst) ASSERT(symbolNode->getQualifier() != EvqGlobal ||
{ (expression->getQualifier() == EvqConst &&
// For variables which are not constant, defer their real initialization until expression->getAsConstantUnion() != nullptr));
// after we initialize uniforms. if (writeSameSymbolInitializer(out, symbolNode, expression))
TIntermBinary *deferredInit = new TIntermBinary(EOpAssign);
deferredInit->setLeft(node->getLeft());
deferredInit->setRight(node->getRight());
deferredInit->setType(node->getType());
mDeferredGlobalInitializers.push_back(deferredInit);
const TString &initString = initializer(node->getType());
node->setRight(new TIntermRaw(node->getType(), initString));
}
else if (writeSameSymbolInitializer(out, symbolNode, expression))
{ {
// Skip initializing the rest of the expression // Skip initializing the rest of the expression
return false; return false;
...@@ -2994,13 +2981,7 @@ bool OutputHLSL::visitSelection(Visit visit, TIntermSelection *node) ...@@ -2994,13 +2981,7 @@ bool OutputHLSL::visitSelection(Visit visit, TIntermSelection *node)
TInfoSinkBase &out = getInfoSink(); TInfoSinkBase &out = getInfoSink();
ASSERT(!node->usesTernaryOperator()); ASSERT(!node->usesTernaryOperator());
ASSERT(mInsideFunction);
if (!mInsideFunction)
{
// This is part of unfolded global initialization.
mDeferredGlobalInitializers.push_back(node);
return false;
}
// D3D errors when there is a gradient operation in a loop in an unflattened if. // D3D errors when there is a gradient operation in a loop in an unflattened if.
if (mShaderType == GL_FRAGMENT_SHADER && mCurrentFunctionMetadata->hasGradientLoop(node)) if (mShaderType == GL_FRAGMENT_SHADER && mCurrentFunctionMetadata->hasGradientLoop(node))
...@@ -3731,47 +3712,6 @@ bool OutputHLSL::writeConstantInitialization(TInfoSinkBase &out, ...@@ -3731,47 +3712,6 @@ bool OutputHLSL::writeConstantInitialization(TInfoSinkBase &out,
return false; return false;
} }
void OutputHLSL::writeDeferredGlobalInitializers(TInfoSinkBase &out)
{
out << "#define ANGLE_USES_DEFERRED_INIT\n"
<< "\n"
<< "void initializeDeferredGlobals()\n"
<< "{\n";
for (const auto &deferredGlobal : mDeferredGlobalInitializers)
{
TIntermBinary *binary = deferredGlobal->getAsBinaryNode();
TIntermSelection *selection = deferredGlobal->getAsSelectionNode();
if (binary != nullptr)
{
TIntermSymbol *symbol = binary->getLeft()->getAsSymbolNode();
TIntermTyped *expression = binary->getRight();
ASSERT(symbol);
ASSERT(symbol->getQualifier() == EvqGlobal && expression->getQualifier() != EvqConst);
out << " " << Decorate(symbol->getSymbol()) << " = ";
if (!writeSameSymbolInitializer(out, symbol, expression))
{
ASSERT(mInfoSinkStack.top() == &out);
expression->traverse(this);
}
out << ";\n";
}
else if (selection != nullptr)
{
writeSelection(out, selection);
}
else
{
UNREACHABLE();
}
}
out << "}\n"
<< "\n";
}
TString OutputHLSL::addStructEqualityFunction(const TStructure &structure) TString OutputHLSL::addStructEqualityFunction(const TStructure &structure)
{ {
const TFieldList &fields = structure.fields(); const TFieldList &fields = structure.fields();
......
...@@ -203,11 +203,6 @@ class OutputHLSL : public TIntermTraverser ...@@ -203,11 +203,6 @@ class OutputHLSL : public TIntermTraverser
std::map<TIntermTyped*, TString> mFlaggedStructMappedNames; std::map<TIntermTyped*, TString> mFlaggedStructMappedNames;
std::map<TIntermTyped*, TString> mFlaggedStructOriginalNames; std::map<TIntermTyped*, TString> mFlaggedStructOriginalNames;
// Some initializers may have been unfolded into if statements, thus we can't evaluate all initializers
// at global static scope in HLSL. Instead, we can initialize these static globals inside a helper function.
// This also enables initialization of globals with uniforms.
TIntermSequence mDeferredGlobalInitializers;
struct HelperFunction struct HelperFunction
{ {
TString functionName; TString functionName;
......
...@@ -412,7 +412,6 @@ bool DynamicHLSL::generateShaderLinkHLSL(const gl::Data &data, ...@@ -412,7 +412,6 @@ bool DynamicHLSL::generateShaderLinkHLSL(const gl::Data &data,
ASSERT(pixelHLSL->empty() && vertexHLSL->empty()); ASSERT(pixelHLSL->empty() && vertexHLSL->empty());
const gl::Shader *vertexShaderGL = programData.getAttachedVertexShader(); const gl::Shader *vertexShaderGL = programData.getAttachedVertexShader();
const ShaderD3D *vertexShader = GetImplAs<ShaderD3D>(vertexShaderGL);
const gl::Shader *fragmentShaderGL = programData.getAttachedFragmentShader(); const gl::Shader *fragmentShaderGL = programData.getAttachedFragmentShader();
const ShaderD3D *fragmentShader = GetImplAs<ShaderD3D>(fragmentShaderGL); const ShaderD3D *fragmentShader = GetImplAs<ShaderD3D>(fragmentShaderGL);
const int shaderModel = mRenderer->getMajorShaderModel(); const int shaderModel = mRenderer->getMajorShaderModel();
...@@ -451,12 +450,6 @@ bool DynamicHLSL::generateShaderLinkHLSL(const gl::Data &data, ...@@ -451,12 +450,6 @@ bool DynamicHLSL::generateShaderLinkHLSL(const gl::Data &data,
<< "{\n" << "{\n"
<< " initAttributes(input);\n"; << " initAttributes(input);\n";
if (vertexShader->usesDeferredInit())
{
vertexStream << "\n"
<< " initializeDeferredGlobals();\n";
}
vertexStream << "\n" vertexStream << "\n"
<< " gl_main();\n" << " gl_main();\n"
<< "\n" << "\n"
...@@ -771,12 +764,6 @@ bool DynamicHLSL::generateShaderLinkHLSL(const gl::Data &data, ...@@ -771,12 +764,6 @@ bool DynamicHLSL::generateShaderLinkHLSL(const gl::Data &data,
pixelStream << ";\n"; pixelStream << ";\n";
} }
if (fragmentShader->usesDeferredInit())
{
pixelStream << "\n"
<< " initializeDeferredGlobals();\n";
}
pixelStream << "\n" pixelStream << "\n"
<< " gl_main();\n" << " gl_main();\n"
<< "\n" << "\n"
......
...@@ -72,7 +72,6 @@ void ShaderD3D::uncompile() ...@@ -72,7 +72,6 @@ void ShaderD3D::uncompile()
mUsesFragDepth = false; mUsesFragDepth = false;
mUsesDiscardRewriting = false; mUsesDiscardRewriting = false;
mUsesNestedBreak = false; mUsesNestedBreak = false;
mUsesDeferredInit = false;
mRequiresIEEEStrictCompiling = false; mRequiresIEEEStrictCompiling = false;
mDebugInfo.clear(); mDebugInfo.clear();
...@@ -171,7 +170,6 @@ bool ShaderD3D::postTranslateCompile(gl::Compiler *compiler, std::string *infoLo ...@@ -171,7 +170,6 @@ bool ShaderD3D::postTranslateCompile(gl::Compiler *compiler, std::string *infoLo
mUsesDiscardRewriting = mUsesDiscardRewriting =
translatedSource.find("ANGLE_USES_DISCARD_REWRITING") != std::string::npos; translatedSource.find("ANGLE_USES_DISCARD_REWRITING") != std::string::npos;
mUsesNestedBreak = translatedSource.find("ANGLE_USES_NESTED_BREAK") != std::string::npos; mUsesNestedBreak = translatedSource.find("ANGLE_USES_NESTED_BREAK") != std::string::npos;
mUsesDeferredInit = translatedSource.find("ANGLE_USES_DEFERRED_INIT") != std::string::npos;
mRequiresIEEEStrictCompiling = mRequiresIEEEStrictCompiling =
translatedSource.find("ANGLE_REQUIRES_IEEE_STRICT_COMPILING") != std::string::npos; translatedSource.find("ANGLE_REQUIRES_IEEE_STRICT_COMPILING") != std::string::npos;
......
...@@ -55,7 +55,6 @@ class ShaderD3D : public ShaderImpl ...@@ -55,7 +55,6 @@ class ShaderD3D : public ShaderImpl
bool usesPointCoord() const { return mUsesPointCoord; } bool usesPointCoord() const { return mUsesPointCoord; }
bool usesDepthRange() const { return mUsesDepthRange; } bool usesDepthRange() const { return mUsesDepthRange; }
bool usesFragDepth() const { return mUsesFragDepth; } bool usesFragDepth() const { return mUsesFragDepth; }
bool usesDeferredInit() const { return mUsesDeferredInit; }
ShShaderOutput getCompilerOutputType() const; ShShaderOutput getCompilerOutputType() const;
...@@ -71,7 +70,6 @@ class ShaderD3D : public ShaderImpl ...@@ -71,7 +70,6 @@ class ShaderD3D : public ShaderImpl
bool mUsesFragDepth; bool mUsesFragDepth;
bool mUsesDiscardRewriting; bool mUsesDiscardRewriting;
bool mUsesNestedBreak; bool mUsesNestedBreak;
bool mUsesDeferredInit;
bool mRequiresIEEEStrictCompiling; bool mRequiresIEEEStrictCompiling;
ShShaderOutput mCompilerOutputType; ShShaderOutput mCompilerOutputType;
......
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