Commit 66ebd014 by maxvujovic@gmail.com

Add the SH_TIMING_RESTRICTIONS compile flag and dependency graph implementation.

Description of the algorithm: http://code.google.com/p/mvujovic/wiki/ShaderControlFlowAnalysis This flag is one potential solution to timing attacks on textures containing cross-domain content or user agent data. This kind of analysis could be useful for both WebGL and CSS Shaders. The SH_TIMING_RESTRICTIONS flag will reject a shader if it uses texture dependent data to affect control flow. Other ways of affecting shader timing such as using NaNs in basic arithmetic operations or using built-in functions (e.g. atan) with different inputs are still under investigation. Issue=329 Review URL: http://codereview.appspot.com/6195062/ git-svn-id: https://angleproject.googlecode.com/svn/trunk@1101 736b8ea6-26fd-11df-bfd4-992fa37f6226
parent 85a250ed
......@@ -39,6 +39,10 @@ Mozilla Corp.
Apple Inc.
David Kilzer
Adobe Systems Inc.
Alexandru Chiculita
Max Vujovic
Aitor Moreno <aitormoreno at gmail.com>
Jim Hauxwell <james at dattrax.co.uk>
ddefrostt
......
......@@ -34,7 +34,7 @@ extern "C" {
// Version number for shader translation API.
// It is incremented everytime the API changes.
#define SH_VERSION 105
#define SH_VERSION 106
//
// The names of the following enums have been derived by replacing GL prefix
......@@ -104,7 +104,23 @@ typedef enum {
SH_UNROLL_FOR_LOOP_WITH_INTEGER_INDEX = 0x0080,
// This is needed only as a workaround for certain OpenGL driver bugs.
SH_EMULATE_BUILT_IN_FUNCTIONS = 0x0100
SH_EMULATE_BUILT_IN_FUNCTIONS = 0x0100,
// This is an experimental flag to enforce restrictions that aim to prevent
// timing attacks.
// It generates compilation errors for shaders that could expose sensitive
// texture information via the timing channel.
// To use this flag, you must compile the shader under the WebGL spec
// (using the SH_WEBGL_SPEC flag).
SH_TIMING_RESTRICTIONS = 0x0200,
// This flag prints the dependency graph that is used to enforce timing
// restrictions on fragment shaders.
// This flag only has an effect if all of the following are true:
// - The shader spec is SH_WEBGL_SPEC.
// - The compile options contain the SH_TIMING_RESTRICTIONS flag.
// - The shader type is SH_FRAGMENT_SHADER.
SH_DEPENDENCY_GRAPH = 0x0400
} ShCompileOptions;
//
......
......@@ -67,6 +67,7 @@ int main(int argc, char* argv[])
char* buffer = 0;
int bufferLen = 0;
int numAttribs = 0, numUniforms = 0;
ShShaderSpec spec = SH_GLES2_SPEC;
ShShaderOutput output = SH_ESSL_OUTPUT;
ShInitialize();
......@@ -85,6 +86,17 @@ int main(int argc, char* argv[])
case 'u': compileOptions |= SH_ATTRIBUTES_UNIFORMS; break;
case 'l': compileOptions |= SH_UNROLL_FOR_LOOP_WITH_INTEGER_INDEX; break;
case 'e': compileOptions |= SH_EMULATE_BUILT_IN_FUNCTIONS; break;
case 'd': compileOptions |= SH_DEPENDENCY_GRAPH; break;
case 't': compileOptions |= SH_TIMING_RESTRICTIONS; break;
case 's':
if (argv[0][2] == '=') {
switch (argv[0][3]) {
case 'e': spec = SH_GLES2_SPEC; break;
case 'w': spec = SH_WEBGL_SPEC; break;
default: failCode = EFailUsage;
}
}
break;
case 'b':
if (argv[0][2] == '=') {
switch (argv[0][3]) {
......@@ -116,13 +128,13 @@ int main(int argc, char* argv[])
case SH_VERTEX_SHADER:
if (vertexCompiler == 0)
vertexCompiler = ShConstructCompiler(
SH_VERTEX_SHADER, SH_GLES2_SPEC, output, &resources);
SH_VERTEX_SHADER, spec, output, &resources);
compiler = vertexCompiler;
break;
case SH_FRAGMENT_SHADER:
if (fragmentCompiler == 0)
fragmentCompiler = ShConstructCompiler(
SH_FRAGMENT_SHADER, SH_GLES2_SPEC, output, &resources);
SH_FRAGMENT_SHADER, spec, output, &resources);
compiler = fragmentCompiler;
break;
default: break;
......@@ -196,6 +208,10 @@ void usage()
" -u : print active attribs and uniforms\n"
" -l : unroll for-loops with integer indices\n"
" -e : emulate certain built-in functions (workaround for driver bugs)\n"
" -t : enforce experimental timing restrictions\n"
" -d : print dependency graph used to enforce timing restrictions\n"
" -s=e : use GLES2 spec (this is by default)\n"
" -s=w : use WebGL spec\n"
" -b=e : output GLSL ES code (this is by default)\n"
" -b=g : output GLSL code\n"
" -b=h : output HLSL code\n"
......
......@@ -126,6 +126,19 @@
'compiler/preprocessor/symbols.h',
'compiler/preprocessor/tokens.c',
'compiler/preprocessor/tokens.h',
# Dependency graph
'compiler/depgraph/DependencyGraph.cpp',
'compiler/depgraph/DependencyGraph.h',
'compiler/depgraph/DependencyGraphBuilder.cpp',
'compiler/depgraph/DependencyGraphBuilder.h',
'compiler/depgraph/DependencyGraphOutput.cpp',
'compiler/depgraph/DependencyGraphOutput.h',
'compiler/depgraph/DependencyGraphTraverse.cpp',
# Timing restrictions
'compiler/timing/RestrictFragmentShaderTiming.cpp',
'compiler/timing/RestrictFragmentShaderTiming.h',
'compiler/timing/RestrictVertexShaderTiming.cpp',
'compiler/timing/RestrictVertexShaderTiming.h',
],
'conditions': [
['OS=="win"', {
......
......@@ -12,6 +12,10 @@
#include "compiler/ParseHelper.h"
#include "compiler/ShHandle.h"
#include "compiler/ValidateLimitations.h"
#include "compiler/depgraph/DependencyGraph.h"
#include "compiler/depgraph/DependencyGraphOutput.h"
#include "compiler/timing/RestrictFragmentShaderTiming.h"
#include "compiler/timing/RestrictVertexShaderTiming.h"
namespace {
bool InitializeSymbolTable(
......@@ -161,6 +165,13 @@ bool TCompiler::compile(const char* const shaderStrings[],
if (success && (compileOptions & SH_VALIDATE_LOOP_INDEXING))
success = validateLimitations(root);
// FIXME(mvujovic): For now, we only consider "u_texture" to be a potentially unsafe symbol.
// If we end up using timing restrictions in WebGL and CSS Shaders, we should expose an API
// to pass in the names of other potentially unsafe symbols (e.g. uniforms referencing
// cross-domain textures).
if (success && (compileOptions & SH_TIMING_RESTRICTIONS))
success = enforceTimingRestrictions(root, "u_texture", (compileOptions & SH_DEPENDENCY_GRAPH) != 0);
// Unroll for-loop markup needs to happen after validateLimitations pass.
if (success && (compileOptions & SH_UNROLL_FOR_LOOP_WITH_INTEGER_INDEX))
ForLoopUnroll::MarkForLoopsWithIntegerIndicesForUnrolling(root);
......@@ -241,6 +252,50 @@ bool TCompiler::validateLimitations(TIntermNode* root) {
return validate.numErrors() == 0;
}
bool TCompiler::enforceTimingRestrictions(TIntermNode* root,
const TString& restrictedSymbol,
bool outputGraph)
{
if (shaderSpec != SH_WEBGL_SPEC) {
infoSink.info << "Timing restrictions must be enforced under the WebGL spec.";
return false;
}
if (shaderType == SH_FRAGMENT_SHADER) {
TDependencyGraph graph(root);
// Output any errors first.
bool success = enforceFragmentShaderTimingRestrictions(graph, restrictedSymbol);
// Then, output the dependency graph.
if (outputGraph) {
TDependencyGraphOutput output(infoSink.info);
output.outputAllSpanningTrees(graph);
}
return success;
}
else {
return enforceVertexShaderTimingRestrictions(root, restrictedSymbol);
}
}
bool TCompiler::enforceFragmentShaderTimingRestrictions(const TDependencyGraph& graph,
const TString& restrictedSymbol)
{
RestrictFragmentShaderTiming restrictor(infoSink.info, restrictedSymbol);
restrictor.enforceRestrictions(graph);
return restrictor.numErrors() == 0;
}
bool TCompiler::enforceVertexShaderTimingRestrictions(TIntermNode* root,
const TString& restrictedSymbol)
{
RestrictVertexShaderTiming restrictor(infoSink.info, restrictedSymbol);
restrictor.enforceRestrictions(root);
return restrictor.numErrors() == 0;
}
void TCompiler::collectAttribsUniforms(TIntermNode* root)
{
CollectAttribsUniforms collect(attribs, uniforms);
......
......@@ -24,6 +24,7 @@
class LongNameMap;
class TCompiler;
class TDependencyGraph;
//
// The base class used to back handles returned to the driver.
......@@ -79,6 +80,17 @@ protected:
void mapLongVariableNames(TIntermNode* root);
// Translate to object code.
virtual void translate(TIntermNode* root) = 0;
// Returns true if the shader passes the restrictions that aim to prevent timing attacks.
bool enforceTimingRestrictions(TIntermNode* root,
const TString& restrictedSymbol,
bool outputGraph);
// Returns true if the shader does not define the restricted symbol.
bool enforceVertexShaderTimingRestrictions(TIntermNode* root,
const TString& restrictedSymbol);
// Returns true if the shader does not use the restricted symbol to affect control flow or in
// operations whose time can depend on the input values.
bool enforceFragmentShaderTimingRestrictions(const TDependencyGraph& graph,
const TString& restrictedSymbol);
// Get built-in extensions with default behavior.
const TExtensionBehavior& getExtensionBehavior() const;
......
//
// Copyright (c) 2012 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.
//
#include "compiler/depgraph/DependencyGraph.h"
#include "compiler/depgraph/DependencyGraphBuilder.h"
TDependencyGraph::TDependencyGraph(TIntermNode* intermNode)
{
TDependencyGraphBuilder::build(intermNode, this);
}
TDependencyGraph::~TDependencyGraph()
{
for (TGraphNodeVector::const_iterator iter = mAllNodes.begin(); iter != mAllNodes.end(); ++iter)
{
TGraphNode* node = *iter;
delete node;
}
}
TGraphSymbol* TDependencyGraph::getGlobalSymbolByName(const TString& name) const
{
TSymbolNameMap::const_iterator iter = mGlobalSymbolMap.find(name);
if (iter == mGlobalSymbolMap.end())
return NULL;
TSymbolNamePair pair = *iter;
TGraphSymbol* symbol = pair.second;
return symbol;
}
TGraphArgument* TDependencyGraph::createArgument(TIntermAggregate* intermFunctionCall,
int argumentNumber)
{
TGraphArgument* argument = new TGraphArgument(intermFunctionCall, argumentNumber);
mAllNodes.push_back(argument);
return argument;
}
TGraphFunctionCall* TDependencyGraph::createFunctionCall(TIntermAggregate* intermFunctionCall)
{
TGraphFunctionCall* functionCall = new TGraphFunctionCall(intermFunctionCall);
mAllNodes.push_back(functionCall);
if (functionCall->getIntermFunctionCall()->isUserDefined())
mUserDefinedFunctionCalls.push_back(functionCall);
return functionCall;
}
TGraphSymbol* TDependencyGraph::getOrCreateSymbol(TIntermSymbol* intermSymbol, bool isGlobalSymbol)
{
TSymbolIdMap::const_iterator iter = mSymbolIdMap.find(intermSymbol->getId());
TGraphSymbol* symbol = NULL;
if (iter != mSymbolIdMap.end()) {
TSymbolIdPair pair = *iter;
symbol = pair.second;
} else {
symbol = new TGraphSymbol(intermSymbol);
mAllNodes.push_back(symbol);
TSymbolIdPair pair(intermSymbol->getId(), symbol);
mSymbolIdMap.insert(pair);
if (isGlobalSymbol) {
// We map all symbols in the global scope by name, so traversers of the graph can
// quickly start searches at global symbols with specific names.
TSymbolNamePair pair(intermSymbol->getSymbol(), symbol);
mGlobalSymbolMap.insert(pair);
}
}
return symbol;
}
TGraphSelection* TDependencyGraph::createSelection(TIntermSelection* intermSelection)
{
TGraphSelection* selection = new TGraphSelection(intermSelection);
mAllNodes.push_back(selection);
return selection;
}
TGraphLoop* TDependencyGraph::createLoop(TIntermLoop* intermLoop)
{
TGraphLoop* loop = new TGraphLoop(intermLoop);
mAllNodes.push_back(loop);
return loop;
}
TGraphLogicalOp* TDependencyGraph::createLogicalOp(TIntermBinary* intermLogicalOp)
{
TGraphLogicalOp* logicalOp = new TGraphLogicalOp(intermLogicalOp);
mAllNodes.push_back(logicalOp);
return logicalOp;
}
const char* TGraphLogicalOp::getOpString() const
{
const char* opString = NULL;
switch (getIntermLogicalOp()->getOp()) {
case EOpLogicalAnd: opString = "and"; break;
case EOpLogicalOr: opString = "or"; break;
default: opString = "unknown"; break;
}
return opString;
}
//
// Copyright (c) 2012 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.
//
#ifndef COMPILER_DEPGRAPH_DEPENDENCY_GRAPH_H
#define COMPILER_DEPGRAPH_DEPENDENCY_GRAPH_H
#include "compiler/intermediate.h"
#include <set>
#include <stack>
class TGraphNode;
class TGraphParentNode;
class TGraphArgument;
class TGraphFunctionCall;
class TGraphSymbol;
class TGraphSelection;
class TGraphLoop;
class TGraphLogicalOp;
class TDependencyGraphTraverser;
class TDependencyGraphOutput;
typedef std::set<TGraphNode*> TGraphNodeSet;
typedef std::vector<TGraphNode*> TGraphNodeVector;
typedef std::vector<TGraphFunctionCall*> TFunctionCallVector;
//
// Base class for all dependency graph nodes.
//
class TGraphNode {
public:
TGraphNode(TIntermNode* node) : intermNode(node) {}
virtual ~TGraphNode() {}
virtual void traverse(TDependencyGraphTraverser* graphTraverser);
protected:
TIntermNode* intermNode;
};
//
// Base class for dependency graph nodes that may have children.
//
class TGraphParentNode : public TGraphNode {
public:
TGraphParentNode(TIntermNode* node) : TGraphNode(node) {}
virtual ~TGraphParentNode() {}
void addDependentNode(TGraphNode* node) { if (node != this) mDependentNodes.insert(node); }
virtual void traverse(TDependencyGraphTraverser* graphTraverser);
private:
TGraphNodeSet mDependentNodes;
};
//
// Handle function call arguments.
//
class TGraphArgument : public TGraphParentNode {
public:
TGraphArgument(TIntermAggregate* intermFunctionCall, int argumentNumber)
: TGraphParentNode(intermFunctionCall)
, mArgumentNumber(argumentNumber) {}
virtual ~TGraphArgument() {}
const TIntermAggregate* getIntermFunctionCall() const { return intermNode->getAsAggregate(); }
int getArgumentNumber() const { return mArgumentNumber; }
virtual void traverse(TDependencyGraphTraverser* graphTraverser);
private:
int mArgumentNumber;
};
//
// Handle function calls.
//
class TGraphFunctionCall : public TGraphParentNode {
public:
TGraphFunctionCall(TIntermAggregate* intermFunctionCall)
: TGraphParentNode(intermFunctionCall) {}
virtual ~TGraphFunctionCall() {}
const TIntermAggregate* getIntermFunctionCall() const { return intermNode->getAsAggregate(); }
virtual void traverse(TDependencyGraphTraverser* graphTraverser);
};
//
// Handle symbols.
//
class TGraphSymbol : public TGraphParentNode {
public:
TGraphSymbol(TIntermSymbol* intermSymbol) : TGraphParentNode(intermSymbol) {}
virtual ~TGraphSymbol() {}
const TIntermSymbol* getIntermSymbol() const { return intermNode->getAsSymbolNode(); }
virtual void traverse(TDependencyGraphTraverser* graphTraverser);
};
//
// Handle if statements and ternary operators.
//
class TGraphSelection : public TGraphNode {
public:
TGraphSelection(TIntermSelection* intermSelection) : TGraphNode(intermSelection) {}
virtual ~TGraphSelection() {}
const TIntermSelection* getIntermSelection() const { return intermNode->getAsSelectionNode(); }
virtual void traverse(TDependencyGraphTraverser* graphTraverser);
};
//
// Handle for, do-while, and while loops.
//
class TGraphLoop : public TGraphNode {
public:
TGraphLoop(TIntermLoop* intermLoop) : TGraphNode(intermLoop) {}
virtual ~TGraphLoop() {}
const TIntermLoop* getIntermLoop() const { return intermNode->getAsLoopNode(); }
virtual void traverse(TDependencyGraphTraverser* graphTraverser);
};
//
// Handle logical and, or.
//
class TGraphLogicalOp : public TGraphNode {
public:
TGraphLogicalOp(TIntermBinary* intermLogicalOp) : TGraphNode(intermLogicalOp) {}
virtual ~TGraphLogicalOp() {}
const TIntermBinary* getIntermLogicalOp() const { return intermNode->getAsBinaryNode(); }
const char* getOpString() const;
virtual void traverse(TDependencyGraphTraverser* graphTraverser);
};
//
// A dependency graph of symbols, function calls, conditions etc.
//
// This class provides an interface to the entry points of the dependency graph.
//
// Dependency graph nodes should be created by using one of the provided "create..." methods.
// This class (and nobody else) manages the memory of the created nodes.
// Nodes may not be removed after being added, so all created nodes will exist while the
// TDependencyGraph instance exists.
//
class TDependencyGraph {
public:
TDependencyGraph(TIntermNode* intermNode);
~TDependencyGraph();
TGraphNodeVector::const_iterator begin() const { return mAllNodes.begin(); }
TGraphNodeVector::const_iterator end() const { return mAllNodes.end(); }
TFunctionCallVector::const_iterator beginUserDefinedFunctionCalls() const
{
return mUserDefinedFunctionCalls.begin();
}
TFunctionCallVector::const_iterator endUserDefinedFunctionCalls() const
{
return mUserDefinedFunctionCalls.end();
}
// Returns NULL if the symbol is not found.
TGraphSymbol* getGlobalSymbolByName(const TString& name) const;
TGraphArgument* createArgument(TIntermAggregate* intermFunctionCall, int argumentNumber);
TGraphFunctionCall* createFunctionCall(TIntermAggregate* intermFunctionCall);
TGraphSymbol* getOrCreateSymbol(TIntermSymbol* intermSymbol, bool isGlobalSymbol);
TGraphSelection* createSelection(TIntermSelection* intermSelection);
TGraphLoop* createLoop(TIntermLoop* intermLoop);
TGraphLogicalOp* createLogicalOp(TIntermBinary* intermLogicalOp);
private:
typedef TMap<int, TGraphSymbol*> TSymbolIdMap;
typedef std::pair<int, TGraphSymbol*> TSymbolIdPair;
typedef TMap<TString, TGraphSymbol*> TSymbolNameMap;
typedef std::pair<TString, TGraphSymbol*> TSymbolNamePair;
TSymbolIdMap mSymbolIdMap;
TSymbolNameMap mGlobalSymbolMap;
TFunctionCallVector mUserDefinedFunctionCalls;
TGraphNodeVector mAllNodes;
};
//
// For traversing the dependency graph. Users should derive from this,
// put their traversal specific data in it, and then pass it to a
// traverse method.
//
// When using this, just fill in the methods for nodes you want visited.
//
class TDependencyGraphTraverser {
public:
TDependencyGraphTraverser() : mDepth(0) {}
virtual void visitSymbol(TGraphSymbol* symbol) {};
virtual void visitArgument(TGraphArgument* selection) {};
virtual void visitFunctionCall(TGraphFunctionCall* functionCall) {};
virtual void visitSelection(TGraphSelection* selection) {};
virtual void visitLoop(TGraphLoop* loop) {};
virtual void visitLogicalOp(TGraphLogicalOp* logicalOp) {};
int getDepth() const { return mDepth; }
void incrementDepth() { ++mDepth; }
void decrementDepth() { --mDepth; }
void clearVisited() { mVisited.clear(); }
void markVisited(TGraphNode* node) { mVisited.insert(node); }
bool isVisited(TGraphNode* node) const { return mVisited.find(node) != mVisited.end(); }
private:
int mDepth;
TGraphNodeSet mVisited;
};
#endif
//
// Copyright (c) 2012 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.
//
#include "compiler/depgraph/DependencyGraphBuilder.h"
TDependencyGraphBuilder::TLeftmostSymbolMaintainer::TSubtreePlaceholder
TDependencyGraphBuilder::TLeftmostSymbolMaintainer::kLeftSubtree;
TDependencyGraphBuilder::TLeftmostSymbolMaintainer::TSubtreePlaceholder
TDependencyGraphBuilder::TLeftmostSymbolMaintainer::kRightSubtree;
void TDependencyGraphBuilder::build(TIntermNode* node, TDependencyGraph* graph)
{
TDependencyGraphBuilder builder(graph);
builder.build(node);
}
bool TDependencyGraphBuilder::visitAggregate(Visit visit, TIntermAggregate* intermAggregate)
{
switch (intermAggregate->getOp()) {
case EOpFunction: visitFunctionDefinition(intermAggregate); break;
case EOpFunctionCall: visitFunctionCall(intermAggregate); break;
default: visitAggregateChildren(intermAggregate); break;
}
return false;
}
void TDependencyGraphBuilder::visitFunctionDefinition(TIntermAggregate* intermAggregate)
{
// Function defintions should only exist in the global scope.
ASSERT(mIsGlobalScope);
// Currently, we do not support user defined functions.
if (intermAggregate->getName() != "main(")
return;
mIsGlobalScope = false;
visitAggregateChildren(intermAggregate);
mIsGlobalScope = true;
}
// Takes an expression like "f(x)" and creates a dependency graph like
// "x -> argument 0 -> function call".
void TDependencyGraphBuilder::visitFunctionCall(TIntermAggregate* intermFunctionCall)
{
TGraphFunctionCall* functionCall = mGraph->createFunctionCall(intermFunctionCall);
// Run through the function call arguments.
int argumentNumber = 0;
TIntermSequence& intermArguments = intermFunctionCall->getSequence();
for (TIntermSequence::const_iterator iter = intermArguments.begin();
iter != intermArguments.end();
++iter, ++argumentNumber)
{
TNodeSetMaintainer nodeSetMaintainer(this);
TIntermNode* intermArgument = *iter;
intermArgument->traverse(this);
if (TParentNodeSet* argumentNodes = mNodeSets.getTopSet()) {
TGraphArgument* argument = mGraph->createArgument(intermFunctionCall, argumentNumber);
connectMultipleNodesToSingleNode(argumentNodes, argument);
argument->addDependentNode(functionCall);
}
}
// Push the leftmost symbol of this function call into the current set of dependent symbols to
// represent the result of this function call.
// Thus, an expression like "y = f(x)" will yield a dependency graph like
// "x -> argument 0 -> function call -> y".
// This line essentially passes the function call node back up to an earlier visitAssignment
// call, which will create the connection "function call -> y".
mNodeSets.insertIntoTopSet(functionCall);
}
void TDependencyGraphBuilder::visitAggregateChildren(TIntermAggregate* intermAggregate)
{
TIntermSequence& sequence = intermAggregate->getSequence();
for(TIntermSequence::const_iterator iter = sequence.begin(); iter != sequence.end(); ++iter)
{
TIntermNode* intermChild = *iter;
intermChild->traverse(this);
}
}
void TDependencyGraphBuilder::visitSymbol(TIntermSymbol* intermSymbol)
{
// Push this symbol into the set of dependent symbols for the current assignment or condition
// that we are traversing.
TGraphSymbol* symbol = mGraph->getOrCreateSymbol(intermSymbol, mIsGlobalScope);
mNodeSets.insertIntoTopSet(symbol);
// If this symbol is the current leftmost symbol under an assignment, replace the previous
// leftmost symbol with this symbol.
if (!mLeftmostSymbols.empty() && mLeftmostSymbols.top() !=
&TLeftmostSymbolMaintainer::kRightSubtree) {
mLeftmostSymbols.pop();
mLeftmostSymbols.push(symbol);
}
}
bool TDependencyGraphBuilder::visitBinary(Visit visit, TIntermBinary* intermBinary)
{
TOperator op = intermBinary->getOp();
if (op == EOpInitialize || intermBinary->modifiesState())
visitAssignment(intermBinary);
else if (op == EOpLogicalAnd || op == EOpLogicalOr)
visitLogicalOp(intermBinary);
else
visitBinaryChildren(intermBinary);
return false;
}
void TDependencyGraphBuilder::visitAssignment(TIntermBinary* intermAssignment)
{
TIntermTyped* intermLeft = intermAssignment->getLeft();
if (!intermLeft)
return;
TGraphSymbol* leftmostSymbol = NULL;
{
TNodeSetMaintainer nodeSetMaintainer(this);
{
TLeftmostSymbolMaintainer leftmostSymbolMaintainer(this, TLeftmostSymbolMaintainer::kLeftSubtree);
intermLeft->traverse(this);
leftmostSymbol = mLeftmostSymbols.top();
// After traversing the left subtree of this assignment, we should have found a real
// leftmost symbol, and the leftmost symbol should not be a placeholder.
ASSERT(leftmostSymbol != &TLeftmostSymbolMaintainer::kLeftSubtree);
ASSERT(leftmostSymbol != &TLeftmostSymbolMaintainer::kRightSubtree);
}
if (TIntermTyped* intermRight = intermAssignment->getRight()) {
TLeftmostSymbolMaintainer leftmostSymbolMaintainer(this, TLeftmostSymbolMaintainer::kRightSubtree);
intermRight->traverse(this);
}
if (TParentNodeSet* assignmentNodes = mNodeSets.getTopSet())
connectMultipleNodesToSingleNode(assignmentNodes, leftmostSymbol);
}
// Push the leftmost symbol of this assignment into the current set of dependent symbols to
// represent the result of this assignment.
// An expression like "a = (b = c)" will yield a dependency graph like "c -> b -> a".
// This line essentially passes the leftmost symbol of the nested assignment ("b" in this
// example) back up to the earlier visitAssignment call for the outer assignment, which will
// create the connection "b -> a".
mNodeSets.insertIntoTopSet(leftmostSymbol);
}
void TDependencyGraphBuilder::visitLogicalOp(TIntermBinary* intermLogicalOp)
{
if (TIntermTyped* intermLeft = intermLogicalOp->getLeft()) {
TNodeSetPropagatingMaintainer nodeSetMaintainer(this);
intermLeft->traverse(this);
if (TParentNodeSet* leftNodes = mNodeSets.getTopSet()) {
TGraphLogicalOp* logicalOp = mGraph->createLogicalOp(intermLogicalOp);
connectMultipleNodesToSingleNode(leftNodes, logicalOp);
}
}
if (TIntermTyped* intermRight = intermLogicalOp->getRight()) {
TLeftmostSymbolMaintainer leftmostSymbolMaintainer(this, TLeftmostSymbolMaintainer::kRightSubtree);
intermRight->traverse(this);
}
}
void TDependencyGraphBuilder::visitBinaryChildren(TIntermBinary* intermBinary)
{
if (TIntermTyped* intermLeft = intermBinary->getLeft())
intermLeft->traverse(this);
if (TIntermTyped* intermRight = intermBinary->getRight()) {
TLeftmostSymbolMaintainer leftmostSymbolMaintainer(this, TLeftmostSymbolMaintainer::kRightSubtree);
intermRight->traverse(this);
}
}
bool TDependencyGraphBuilder::visitSelection(Visit visit, TIntermSelection* intermSelection)
{
if (TIntermNode* intermCondition = intermSelection->getCondition()) {
TNodeSetMaintainer nodeSetMaintainer(this);
intermCondition->traverse(this);
if (TParentNodeSet* conditionNodes = mNodeSets.getTopSet()) {
TGraphSelection* selection = mGraph->createSelection(intermSelection);
connectMultipleNodesToSingleNode(conditionNodes, selection);
}
}
if (TIntermNode* intermTrueBlock = intermSelection->getTrueBlock())
intermTrueBlock->traverse(this);
if (TIntermNode* intermFalseBlock = intermSelection->getFalseBlock())
intermFalseBlock->traverse(this);
return false;
}
bool TDependencyGraphBuilder::visitLoop(Visit visit, TIntermLoop* intermLoop)
{
if (TIntermTyped* intermCondition = intermLoop->getCondition()) {
TNodeSetMaintainer nodeSetMaintainer(this);
intermCondition->traverse(this);
if (TParentNodeSet* conditionNodes = mNodeSets.getTopSet()) {
TGraphLoop* loop = mGraph->createLoop(intermLoop);
connectMultipleNodesToSingleNode(conditionNodes, loop);
}
}
if (TIntermNode* intermBody = intermLoop->getBody())
intermBody->traverse(this);
if (TIntermTyped* intermExpression = intermLoop->getExpression())
intermExpression->traverse(this);
return false;
}
void TDependencyGraphBuilder::connectMultipleNodesToSingleNode(TParentNodeSet* nodes,
TGraphNode* node) const
{
for (TParentNodeSet::const_iterator iter = nodes->begin(); iter != nodes->end(); ++iter)
{
TGraphParentNode* currentNode = *iter;
currentNode->addDependentNode(node);
}
}
//
// Copyright (c) 2012 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.
//
#ifndef COMPILER_DEPGRAPH_DEPENDENCY_GRAPH_BUILDER_H
#define COMPILER_DEPGRAPH_DEPENDENCY_GRAPH_BUILDER_H
#include "compiler/depgraph/DependencyGraph.h"
//
// Creates a dependency graph of symbols, function calls, conditions etc. by traversing a
// intermediate tree.
//
class TDependencyGraphBuilder : public TIntermTraverser {
public:
static void build(TIntermNode* node, TDependencyGraph* graph);
virtual void visitSymbol(TIntermSymbol*);
virtual bool visitBinary(Visit visit, TIntermBinary*);
virtual bool visitSelection(Visit visit, TIntermSelection*);
virtual bool visitAggregate(Visit visit, TIntermAggregate*);
virtual bool visitLoop(Visit visit, TIntermLoop*);
private:
typedef std::stack<TGraphSymbol*> TSymbolStack;
typedef std::set<TGraphParentNode*> TParentNodeSet;
//
// For collecting the dependent nodes of assignments, conditions, etc.
// while traversing the intermediate tree.
//
// This data structure is stack of sets. Each set contains dependency graph parent nodes.
//
class TNodeSetStack {
public:
TNodeSetStack() {};
~TNodeSetStack() { clear(); }
// This should only be called after a pushSet.
// Returns NULL if the top set is empty.
TParentNodeSet* getTopSet() const
{
ASSERT(!nodeSets.empty());
TParentNodeSet* topSet = nodeSets.top();
return !topSet->empty() ? topSet : NULL;
}
void pushSet() { nodeSets.push(new TParentNodeSet()); }
void popSet()
{
ASSERT(!nodeSets.empty());
delete nodeSets.top();
nodeSets.pop();
}
// Pops the top set and adds its contents to the new top set.
// This should only be called after a pushSet.
// If there is no set below the top set, the top set is just deleted.
void popSetIntoNext()
{
ASSERT(!nodeSets.empty());
TParentNodeSet* oldTopSet = nodeSets.top();
nodeSets.pop();
if (!nodeSets.empty()) {
TParentNodeSet* newTopSet = nodeSets.top();
newTopSet->insert(oldTopSet->begin(), oldTopSet->end());
}
delete oldTopSet;
}
// Does nothing if there is no top set.
// This can be called when there is no top set if we are visiting
// symbols that are not under an assignment or condition.
// We don't need to track those symbols.
void insertIntoTopSet(TGraphParentNode* node)
{
if (nodeSets.empty())
return;
nodeSets.top()->insert(node);
}
void clear()
{
while (!nodeSets.empty())
popSet();
}
private:
typedef std::stack<TParentNodeSet*> TParentNodeSetStack;
TParentNodeSetStack nodeSets;
};
//
// An instance of this class pushes a new node set when instantiated.
// When the instance goes out of scope, it and pops the node set.
//
class TNodeSetMaintainer {
public:
TNodeSetMaintainer(TDependencyGraphBuilder* factory)
: sets(factory->mNodeSets) { sets.pushSet(); }
~TNodeSetMaintainer() { sets.popSet(); }
protected:
TNodeSetStack& sets;
};
//
// An instance of this class pushes a new node set when instantiated.
// When the instance goes out of scope, it and pops the top node set and adds its contents to
// the new top node set.
//
class TNodeSetPropagatingMaintainer {
public:
TNodeSetPropagatingMaintainer(TDependencyGraphBuilder* factory)
: sets(factory->mNodeSets) { sets.pushSet(); }
~TNodeSetPropagatingMaintainer() { sets.popSetIntoNext(); }
protected:
TNodeSetStack& sets;
};
//
// An instance of this class keeps track of the leftmost symbol while we're exploring an
// assignment.
// It will push the placeholder symbol kLeftSubtree when instantiated under a left subtree,
// and kRightSubtree under a right subtree.
// When it goes out of scope, it will pop the leftmost symbol at the top of the scope.
// During traversal, the TDependencyGraphBuilder will replace kLeftSubtree with a real symbol.
// kRightSubtree will never be replaced by a real symbol because we are tracking the leftmost
// symbol.
//
class TLeftmostSymbolMaintainer {
public:
class TSubtreePlaceholder : public TGraphSymbol {
public:
TSubtreePlaceholder() : TGraphSymbol(NULL) {}
};
static TSubtreePlaceholder kLeftSubtree;
static TSubtreePlaceholder kRightSubtree;
TLeftmostSymbolMaintainer(TDependencyGraphBuilder* factory, TSubtreePlaceholder& subtree)
: leftmostSymbols(factory->mLeftmostSymbols)
{
needsPlaceholderSymbol = leftmostSymbols.empty() || leftmostSymbols.top() != &subtree;
if (needsPlaceholderSymbol)
leftmostSymbols.push(&subtree);
}
~TLeftmostSymbolMaintainer()
{
if (needsPlaceholderSymbol)
leftmostSymbols.pop();
}
protected:
TSymbolStack& leftmostSymbols;
bool needsPlaceholderSymbol;
};
TDependencyGraphBuilder(TDependencyGraph* graph)
: TIntermTraverser(true, false, false)
, mGraph(graph)
, mIsGlobalScope(true) {}
void build(TIntermNode* intermNode) { intermNode->traverse(this); }
void connectMultipleNodesToSingleNode(TParentNodeSet* nodes, TGraphNode* node) const;
void visitAssignment(TIntermBinary*);
void visitLogicalOp(TIntermBinary*);
void visitBinaryChildren(TIntermBinary*);
void visitFunctionDefinition(TIntermAggregate*);
void visitFunctionCall(TIntermAggregate* intermFunctionCall);
void visitAggregateChildren(TIntermAggregate*);
TDependencyGraph* mGraph;
TNodeSetStack mNodeSets;
TSymbolStack mLeftmostSymbols;
bool mIsGlobalScope;
};
#endif // COMPILER_DEPGRAPH_DEPENDENCY_GRAPH_BUILDER_H
//
// Copyright (c) 2012 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.
//
#include "compiler/depgraph/DependencyGraphOutput.h"
void TDependencyGraphOutput::outputIndentation()
{
for (int i = 0; i < getDepth(); ++i)
mSink << " ";
}
void TDependencyGraphOutput::visitArgument(TGraphArgument* parameter)
{
outputIndentation();
mSink << "argument " << parameter->getArgumentNumber() << " of call to "
<< parameter->getIntermFunctionCall()->getName() << "\n";
}
void TDependencyGraphOutput::visitFunctionCall(TGraphFunctionCall* functionCall)
{
outputIndentation();
mSink << "function call " << functionCall->getIntermFunctionCall()->getName() << "\n";
}
void TDependencyGraphOutput::visitSymbol(TGraphSymbol* symbol)
{
outputIndentation();
mSink << symbol->getIntermSymbol()->getSymbol() << " (symbol id: "
<< symbol->getIntermSymbol()->getId() << ")\n";
}
void TDependencyGraphOutput::visitSelection(TGraphSelection* selection)
{
outputIndentation();
mSink << "selection\n";
}
void TDependencyGraphOutput::visitLoop(TGraphLoop* loop)
{
outputIndentation();
mSink << "loop condition\n";
}
void TDependencyGraphOutput::visitLogicalOp(TGraphLogicalOp* logicalOp)
{
outputIndentation();
mSink << "logical " << logicalOp->getOpString() << "\n";
}
void TDependencyGraphOutput::outputAllSpanningTrees(TDependencyGraph& graph)
{
mSink << "\n";
for (TGraphNodeVector::const_iterator iter = graph.begin(); iter != graph.end(); ++iter)
{
TGraphNode* symbol = *iter;
mSink << "--- Dependency graph spanning tree ---\n";
clearVisited();
symbol->traverse(this);
mSink << "\n";
}
}
//
// Copyright (c) 2012 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.
//
#ifndef COMPILER_DEPGRAPH_DEPENDENCY_GRAPH_OUTPUT_H
#define COMPILER_DEPGRAPH_DEPENDENCY_GRAPH_OUTPUT_H
#include "compiler/depgraph/DependencyGraph.h"
#include "compiler/InfoSink.h"
class TDependencyGraphOutput : public TDependencyGraphTraverser {
public:
TDependencyGraphOutput(TInfoSinkBase& sink) : mSink(sink) {}
virtual void visitSymbol(TGraphSymbol* symbol);
virtual void visitArgument(TGraphArgument* parameter);
virtual void visitFunctionCall(TGraphFunctionCall* functionCall);
virtual void visitSelection(TGraphSelection* selection);
virtual void visitLoop(TGraphLoop* loop);
virtual void visitLogicalOp(TGraphLogicalOp* logicalOp);
void outputAllSpanningTrees(TDependencyGraph& graph);
private:
void outputIndentation();
TInfoSinkBase& mSink;
};
#endif // COMPILER_DEPGRAPH_DEPENDENCY_GRAPH_OUTPUT_H
//
// Copyright (c) 2012 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.
//
#include "compiler/depgraph/DependencyGraph.h"
// These methods do a breadth-first traversal through the graph and mark visited nodes.
void TGraphNode::traverse(TDependencyGraphTraverser* graphTraverser)
{
graphTraverser->markVisited(this);
}
void TGraphParentNode::traverse(TDependencyGraphTraverser* graphTraverser)
{
TGraphNode::traverse(graphTraverser);
graphTraverser->incrementDepth();
// Visit the parent node's children.
for (TGraphNodeSet::const_iterator iter = mDependentNodes.begin();
iter != mDependentNodes.end();
++iter)
{
TGraphNode* node = *iter;
if (!graphTraverser->isVisited(node))
node->traverse(graphTraverser);
}
graphTraverser->decrementDepth();
}
void TGraphArgument::traverse(TDependencyGraphTraverser* graphTraverser)
{
graphTraverser->visitArgument(this);
TGraphParentNode::traverse(graphTraverser);
}
void TGraphFunctionCall::traverse(TDependencyGraphTraverser* graphTraverser)
{
graphTraverser->visitFunctionCall(this);
TGraphParentNode::traverse(graphTraverser);
}
void TGraphSymbol::traverse(TDependencyGraphTraverser* graphTraverser)
{
graphTraverser->visitSymbol(this);
TGraphParentNode::traverse(graphTraverser);
}
void TGraphSelection::traverse(TDependencyGraphTraverser* graphTraverser)
{
graphTraverser->visitSelection(this);
TGraphNode::traverse(graphTraverser);
}
void TGraphLoop::traverse(TDependencyGraphTraverser* graphTraverser)
{
graphTraverser->visitLoop(this);
TGraphNode::traverse(graphTraverser);
}
void TGraphLogicalOp::traverse(TDependencyGraphTraverser* graphTraverser)
{
graphTraverser->visitLogicalOp(this);
TGraphNode::traverse(graphTraverser);
}
......@@ -452,7 +452,7 @@ public:
const TString& getName() const { return name; }
void setUserDefined() { userDefined = true; }
bool isUserDefined() { return userDefined; }
bool isUserDefined() const { return userDefined; }
void setOptimize(bool o) { optimize = o; }
bool getOptimize() { return optimize; }
......
//
// Copyright (c) 2012 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.
//
#include "compiler/InfoSink.h"
#include "compiler/ParseHelper.h"
#include "compiler/depgraph/DependencyGraphOutput.h"
#include "compiler/timing/RestrictFragmentShaderTiming.h"
// FIXME(mvujovic): We do not know if the execution time of built-in operations like sin, pow, etc.
// can vary based on the value of the input arguments. If so, we should restrict those as well.
void RestrictFragmentShaderTiming::enforceRestrictions(const TDependencyGraph& graph)
{
mNumErrors = 0;
// FIXME(mvujovic): The dependency graph does not support user defined function calls right now,
// so we generate errors for them.
validateUserDefinedFunctionCallUsage(graph);
// Traverse the dependency graph starting at s_texture and generate an error each time we hit a
// condition node.
TGraphSymbol* uTextureGraphSymbol = graph.getGlobalSymbolByName(mRestrictedSymbol);
if (uTextureGraphSymbol &&
uTextureGraphSymbol->getIntermSymbol()->getQualifier() == EvqUniform &&
uTextureGraphSymbol->getIntermSymbol()->getBasicType() == EbtSampler2D)
uTextureGraphSymbol->traverse(this);
}
void RestrictFragmentShaderTiming::validateUserDefinedFunctionCallUsage(const TDependencyGraph& graph)
{
for (TFunctionCallVector::const_iterator iter = graph.beginUserDefinedFunctionCalls();
iter != graph.endUserDefinedFunctionCalls();
++iter)
{
TGraphFunctionCall* functionCall = *iter;
beginError(functionCall->getIntermFunctionCall());
mSink << "A call to a user defined function is not permitted.\n";
}
}
void RestrictFragmentShaderTiming::beginError(const TIntermNode* node)
{
++mNumErrors;
mSink.prefix(EPrefixError);
mSink.location(node->getLine());
}
void RestrictFragmentShaderTiming::visitArgument(TGraphArgument* parameter)
{
// FIXME(mvujovic): We should restrict sampler dependent values from being texture coordinates
// in all available sampling operationsn supported in GLSL ES.
// This includes overloaded signatures of texture2D, textureCube, and others.
if (parameter->getIntermFunctionCall()->getName() != "texture2D(s21;vf2;" ||
parameter->getArgumentNumber() != 1)
return;
beginError(parameter->getIntermFunctionCall());
mSink << "An expression dependent on a uniform sampler2D by the name '" << mRestrictedSymbol
<< "' is not permitted to be the second argument of a texture2D call.\n";
}
void RestrictFragmentShaderTiming::visitSelection(TGraphSelection* selection)
{
beginError(selection->getIntermSelection());
mSink << "An expression dependent on a uniform sampler2D by the name '" << mRestrictedSymbol
<< "' is not permitted in a conditional statement.\n";
}
void RestrictFragmentShaderTiming::visitLoop(TGraphLoop* loop)
{
beginError(loop->getIntermLoop());
mSink << "An expression dependent on a uniform sampler2D by the name '" << mRestrictedSymbol
<< "' is not permitted in a loop condition.\n";
}
void RestrictFragmentShaderTiming::visitLogicalOp(TGraphLogicalOp* logicalOp)
{
beginError(logicalOp->getIntermLogicalOp());
mSink << "An expression dependent on a uniform sampler2D by the name '" << mRestrictedSymbol
<< "' is not permitted on the left hand side of a logical " << logicalOp->getOpString()
<< " operator.\n";
}
//
// Copyright (c) 2012 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.
//
#ifndef COMPILER_TIMING_RESTRICT_FRAGMENT_SHADER_TIMING_H_
#define COMPILER_TIMING_RESTRICT_FRAGMENT_SHADER_TIMING_H_
#include "GLSLANG/ShaderLang.h"
#include "compiler/intermediate.h"
#include "compiler/depgraph/DependencyGraph.h"
class TInfoSinkBase;
class RestrictFragmentShaderTiming : TDependencyGraphTraverser {
public:
RestrictFragmentShaderTiming(TInfoSinkBase& sink, const TString& restrictedSymbol)
: mSink(sink)
, mRestrictedSymbol(restrictedSymbol)
, mNumErrors(0) {}
void enforceRestrictions(const TDependencyGraph& graph);
int numErrors() const { return mNumErrors; }
virtual void visitArgument(TGraphArgument* parameter);
virtual void visitSelection(TGraphSelection* selection);
virtual void visitLoop(TGraphLoop* loop);
virtual void visitLogicalOp(TGraphLogicalOp* logicalOp);
private:
void beginError(const TIntermNode* node);
void validateUserDefinedFunctionCallUsage(const TDependencyGraph& graph);
TInfoSinkBase& mSink;
const TString mRestrictedSymbol;
int mNumErrors;
};
#endif // COMPILER_TIMING_RESTRICT_FRAGMENT_SHADER_TIMING_H_
//
// Copyright (c) 2012 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.
//
#include "compiler/timing/RestrictVertexShaderTiming.h"
void RestrictVertexShaderTiming::visitSymbol(TIntermSymbol* node)
{
if (node->getQualifier() == EvqUniform &&
node->getBasicType() == EbtSampler2D &&
node->getSymbol() == mRestrictedSymbol) {
mFoundRestrictedSymbol = true;
mSink.prefix(EPrefixError);
mSink.location(node->getLine());
mSink << "Definition of a uniform sampler2D by the name '" << mRestrictedSymbol
<< "' is not permitted in vertex shaders.\n";
}
}
bool RestrictVertexShaderTiming::visitAggregate(Visit visit, TIntermAggregate* node)
{
// Don't keep exploring if we've found the restricted symbol, and don't explore anything besides
// the global scope (i.e. don't explore function definitions).
if (mFoundRestrictedSymbol || node->getOp() == EOpFunction)
return false;
return true;
}
//
// Copyright (c) 2012 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.
//
#ifndef COMPILER_TIMING_RESTRICT_VERTEX_SHADER_TIMING_H_
#define COMPILER_TIMING_RESTRICT_VERTEX_SHADER_TIMING_H_
#include "GLSLANG/ShaderLang.h"
#include "compiler/intermediate.h"
#include "compiler/InfoSink.h"
class TInfoSinkBase;
class RestrictVertexShaderTiming : public TIntermTraverser {
public:
RestrictVertexShaderTiming(TInfoSinkBase& sink, const TString& restrictedSymbol)
: TIntermTraverser(true, false, false)
, mSink(sink)
, mRestrictedSymbol(restrictedSymbol)
, mFoundRestrictedSymbol(false) {}
void enforceRestrictions(TIntermNode* root) { root->traverse(this); }
int numErrors() { return mFoundRestrictedSymbol ? 1 : 0; }
virtual void visitSymbol(TIntermSymbol*);
virtual bool visitBinary(Visit visit, TIntermBinary*) { return false; }
virtual bool visitUnary(Visit visit, TIntermUnary*) { return false; }
virtual bool visitSelection(Visit visit, TIntermSelection*) { return false; }
virtual bool visitAggregate(Visit visit, TIntermAggregate*);
virtual bool visitLoop(Visit visit, TIntermLoop*) { return false; };
virtual bool visitBranch(Visit visit, TIntermBranch*) { return false; };
private:
TInfoSinkBase& mSink;
const TString mRestrictedSymbol;
bool mFoundRestrictedSymbol;
};
#endif // COMPILER_TIMING_RESTRICT_VERTEX_SHADER_TIMING_H_
......@@ -518,6 +518,38 @@
>
</File>
</Filter>
<Filter
Name="depgraph"
>
<File
RelativePath=".\depgraph\DependencyGraph.cpp"
>
</File>
<File
RelativePath=".\depgraph\DependencyGraphBuilder.cpp"
>
</File>
<File
RelativePath=".\depgraph\DependencyGraphOutput.cpp"
>
</File>
<File
RelativePath=".\depgraph\DependencyGraphTraverse.cpp"
>
</File>
</Filter>
<Filter
Name="timing"
>
<File
RelativePath=".\timing\RestrictFragmentShaderTiming.cpp"
>
</File>
<File
RelativePath=".\timing\RestrictVertexShaderTiming.cpp"
>
</File>
</Filter>
</Filter>
<Filter
Name="Header Files"
......@@ -696,6 +728,34 @@
>
</File>
</Filter>
<Filter
Name="timing"
>
<File
RelativePath=".\timing\RestrictFragmentShaderTiming.h"
>
</File>
<File
RelativePath=".\timing\RestrictVertexShaderTiming.h"
>
</File>
</Filter>
<Filter
Name="depgraph"
>
<File
RelativePath=".\depgraph\DependencyGraph.h"
>
</File>
<File
RelativePath=".\depgraph\DependencyGraphBuilder.h"
>
</File>
<File
RelativePath=".\depgraph\DependencyGraphOutput.h"
>
</File>
</Filter>
</Filter>
</Files>
<Globals>
......
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