blob: dfa23aaf19be48995cc06368b51d01b0a129fd9b [file] [log] [blame]
//
// Copyright 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 separate
// function that is called 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. Note that it's important that the function definition
// is at the end of the shader, as some globals may be declared after main().
//
// It can also initialize all uninitialized globals.
//
#include "compiler/translator/tree_ops/DeferGlobalInitializers.h"
#include <vector>
#include "compiler/translator/Compiler.h"
#include "compiler/translator/IntermNode.h"
#include "compiler/translator/StaticType.h"
#include "compiler/translator/SymbolTable.h"
#include "compiler/translator/tree_ops/InitializeVariables.h"
#include "compiler/translator/tree_util/FindMain.h"
#include "compiler/translator/tree_util/IntermNode_util.h"
#include "compiler/translator/tree_util/ReplaceVariable.h"
namespace sh
{
namespace
{
constexpr const ImmutableString kInitGlobalsString("initGlobals");
void GetDeferredInitializers(TIntermDeclaration *declaration,
bool initializeUninitializedGlobals,
bool canUseLoopsToInitialize,
bool highPrecisionSupported,
TIntermSequence *deferredInitializersOut,
std::vector<const TVariable *> *variablesToReplaceOut,
TSymbolTable *symbolTable)
{
// SeparateDeclarations should have already been run.
ASSERT(declaration->getSequence()->size() == 1);
TIntermNode *declarator = declaration->getSequence()->back();
TIntermBinary *init = declarator->getAsBinaryNode();
if (init)
{
TIntermSymbol *symbolNode = init->getLeft()->getAsSymbolNode();
ASSERT(symbolNode);
TIntermTyped *expression = init->getRight();
if (expression->getQualifier() != EvqConst || !expression->hasConstantValue())
{
// 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 can not be converted to a
// constant union, since otherwise there's a chance that HLSL output will generate extra
// statements from the initializer expression.
// Change const global to a regular global if its initialization is deferred.
// This can happen if ANGLE has not been able to fold the constant expression used
// as an initializer.
ASSERT(symbolNode->getQualifier() == EvqConst ||
symbolNode->getQualifier() == EvqGlobal);
if (symbolNode->getQualifier() == EvqConst)
{
variablesToReplaceOut->push_back(&symbolNode->variable());
}
TIntermBinary *deferredInit =
new TIntermBinary(EOpAssign, symbolNode->deepCopy(), init->getRight());
deferredInitializersOut->push_back(deferredInit);
// Remove the initializer from the global scope and just declare the global instead.
declaration->replaceChildNode(init, symbolNode);
}
}
else if (initializeUninitializedGlobals)
{
TIntermSymbol *symbolNode = declarator->getAsSymbolNode();
ASSERT(symbolNode);
// Ignore ANGLE internal variables and nameless declarations.
if (symbolNode->variable().symbolType() == SymbolType::AngleInternal ||
symbolNode->variable().symbolType() == SymbolType::Empty)
return;
if (symbolNode->getQualifier() == EvqGlobal)
{
TIntermSequence *initCode = CreateInitCode(symbolNode, canUseLoopsToInitialize,
highPrecisionSupported, symbolTable);
deferredInitializersOut->insert(deferredInitializersOut->end(), initCode->begin(),
initCode->end());
}
}
}
void InsertInitCallToMain(TIntermBlock *root,
TIntermSequence *deferredInitializers,
TSymbolTable *symbolTable)
{
TIntermBlock *initGlobalsBlock = new TIntermBlock();
initGlobalsBlock->getSequence()->swap(*deferredInitializers);
TFunction *initGlobalsFunction =
new TFunction(symbolTable, kInitGlobalsString, SymbolType::AngleInternal,
StaticType::GetBasic<EbtVoid>(), false);
TIntermFunctionPrototype *initGlobalsFunctionPrototype =
CreateInternalFunctionPrototypeNode(*initGlobalsFunction);
root->getSequence()->insert(root->getSequence()->begin(), initGlobalsFunctionPrototype);
TIntermFunctionDefinition *initGlobalsFunctionDefinition =
CreateInternalFunctionDefinitionNode(*initGlobalsFunction, initGlobalsBlock);
root->appendStatement(initGlobalsFunctionDefinition);
TIntermAggregate *initGlobalsCall =
TIntermAggregate::CreateFunctionCall(*initGlobalsFunction, new TIntermSequence());
TIntermBlock *mainBody = FindMainBody(root);
mainBody->getSequence()->insert(mainBody->getSequence()->begin(), initGlobalsCall);
}
} // namespace
bool DeferGlobalInitializers(TCompiler *compiler,
TIntermBlock *root,
bool initializeUninitializedGlobals,
bool canUseLoopsToInitialize,
bool highPrecisionSupported,
TSymbolTable *symbolTable)
{
TIntermSequence *deferredInitializers = new TIntermSequence();
std::vector<const TVariable *> variablesToReplace;
// Loop over all global statements and process the declarations. This is simpler than using a
// traverser.
for (TIntermNode *statement : *root->getSequence())
{
TIntermDeclaration *declaration = statement->getAsDeclarationNode();
if (declaration)
{
GetDeferredInitializers(declaration, initializeUninitializedGlobals,
canUseLoopsToInitialize, highPrecisionSupported,
deferredInitializers, &variablesToReplace, symbolTable);
}
}
// Add the function with initialization and the call to that.
if (!deferredInitializers->empty())
{
InsertInitCallToMain(root, deferredInitializers, symbolTable);
}
// Replace constant variables with non-constant global variables.
for (const TVariable *var : variablesToReplace)
{
TType *replacementType = new TType(var->getType());
replacementType->setQualifier(EvqGlobal);
TVariable *replacement =
new TVariable(symbolTable, var->name(), replacementType, var->symbolType());
if (!ReplaceVariable(compiler, root, var, replacement))
{
return false;
}
}
return true;
}
} // namespace sh