Add and partially implement an interface for doing uniform reflection. It includes an AST traversal to identify live accesses.
It does not yet correctly compute block offsets, give correct GL-API-style type values, or handle arrays.
This is tied to the new -q flag.
git-svn-id: https://cvs.khronos.org/svn/repos/ogl/trunk/ecosystem/public/sdk/tools/glslang@23938 e7fa87d3-cd2b-0410-9028-fcbf551c1848
diff --git a/StandAlone/StandAlone.cpp b/StandAlone/StandAlone.cpp
index b3a37dc..a3c1faf 100644
--- a/StandAlone/StandAlone.cpp
+++ b/StandAlone/StandAlone.cpp
@@ -61,6 +61,7 @@
EOptionsLinkProgram = 0x020,
EOptionMultiThreaded = 0x040,
EOptionDumpConfig = 0x080,
+ EOptionDumpReflection = 0x100,
};
//
@@ -466,6 +467,9 @@
case 'm':
Options |= EOptionMemoryLeakMode;
break;
+ case 'q':
+ Options |= EOptionDumpReflection;
+ break;
case 'r':
Options |= EOptionRelaxedErrors;
break;
@@ -575,6 +579,11 @@
puts(program.getInfoDebugLog());
}
+ if (Options & EOptionDumpReflection) {
+ program.buildReflection();
+ program.dumpReflection();
+ }
+
// Free everything up, program has to go before the shaders
// because it might have merged stuff from the shaders, and
// the stuff from the shaders has to have its destructors called
@@ -771,6 +780,7 @@
"-d: delay exit\n"
"-l: link validation of all input files\n"
"-m: memory leak mode\n"
+ "-q: dump reflection query database\n"
"-r: relaxed semantic error-checking mode\n"
"-s: silent mode\n"
"-t: multi-threaded mode\n");
diff --git a/Test/baseResults/reflection.vert.out b/Test/baseResults/reflection.vert.out
new file mode 100644
index 0000000..dd02aea
--- /dev/null
+++ b/Test/baseResults/reflection.vert.out
@@ -0,0 +1,33 @@
+reflection.vert
+Warning, version 440 is not yet complete; some version-specific features are present, but many are missing.
+
+
+
+Linked vertex stage:
+
+
+
+Uniform reflection:
+0:anonMember3: offset 32, type 35666, arraySize 1, index 0
+1:s.a: offset -1, type 35666, arraySize 1, index -1
+2:anonMember1: offset 0, type 35666, arraySize 1, index 0
+3:uf1: offset -1, type 35666, arraySize 1, index -1
+4:uf2: offset -1, type 35666, arraySize 1, index -1
+5:ablock.member3: offset 32, type 35666, arraySize 1, index 1
+
+Uniform block reflection:
+0: nameless: offset -1, type -1, arraySize 1, index -1
+1: ablock: offset -1, type -1, arraySize 1, index -1
+
+Live names
+ablock: 1
+ablock.member3: 5
+anonMember1: 2
+anonMember3: 0
+liveFunction1(: -1
+liveFunction2(: -1
+nameless: 0
+s.a: 1
+uf1: 3
+uf2: 4
+
diff --git a/Test/reflection.vert b/Test/reflection.vert
new file mode 100644
index 0000000..79a6ef8
--- /dev/null
+++ b/Test/reflection.vert
@@ -0,0 +1,73 @@
+#version 440 core
+
+uniform nameless {
+ vec3 anonMember1;
+ vec4 anonDeadMember2;
+ vec4 anonMember3;
+};
+
+uniform named {
+ vec3 deadMember1;
+ vec4 member2;
+ vec4 member3;
+} ablock;
+
+uniform namelessdead {
+ int a;
+};
+
+uniform namedDead {
+ int b;
+} bblock;
+
+struct TS {
+ int a;
+ int dead;
+};
+
+uniform TS s;
+
+uniform float uf1;
+uniform float uf2;
+uniform float ufDead3;
+uniform float ufDead4;
+
+const bool control = true;
+
+void deadFunction()
+{
+ vec3 v3 = ablock.deadMember1;
+ vec4 v = anonDeadMember2;
+ float f = ufDead4;
+}
+
+void liveFunction2()
+{
+ vec3 v = anonMember1;
+ float f = uf1;
+}
+
+void liveFunction1()
+{
+ liveFunction2();
+ float f = uf2;
+ vec4 v = ablock.member3;
+}
+
+void main()
+{
+ liveFunction1();
+ liveFunction2();
+
+ if (! control)
+ deadFunction();
+
+ float f;
+
+ if (control) {
+ liveFunction2();
+ f = anonMember3.z;
+ f = s.a;
+ } else
+ f = ufDead3;
+}
diff --git a/Test/runtests b/Test/runtests
index 5f0e941..9263304 100644
--- a/Test/runtests
+++ b/Test/runtests
@@ -44,6 +44,13 @@
runLinkTest empty.frag empty2.frag empty3.frag
#
+# reflection tests
+#
+echo Running reflection...
+$EXE -l -q reflection.vert > $TARGETDIR/reflection.vert.out
+diff -b $BASEDIR/reflection.vert.out $TARGETDIR/reflection.vert.out
+
+#
# multi-threaded test
#
echo Comparing single thread to multithread for all tests in current directory...
diff --git a/Todo.txt b/Todo.txt
index 29ed8c1..7aff9fe 100644
--- a/Todo.txt
+++ b/Todo.txt
@@ -36,6 +36,8 @@
- Non ES: write to only one of gl_FragColor, gl_FragData, or user-declared
- 1.50: at least one geometry shader says input primitive and at least one says output primitive...
- 1.50: at least one geometry shader says max_vertices...
+ - 1.50: geometry shaders: max_vertices must be checked against gl_MaxGeometryOutputVertices (maybe at compile time)
+ + 1.50: origin_upper_left and pixel_center_integer have to match
- 4.4: An interface contains two different blocks, each with no instance name, where the blocks contain a member with the same name.
- 4.4: component aliasing (except desktop vertex shader inputs)
Intra-stage linking, multiple shader
@@ -90,8 +92,8 @@
+ Add new minimum maximums for gl_MaxVertexOutputComponents, gl_MaxGeometryInputComponents, gl_MaxGeometryOutputComponents, and gl_MaxFragmentInputComponents,
rather than relying on gl_MaxVaryingComponents. Also, corrected gl_MaxVaryingComponents to be 60 instead of 64.
+ Added gl_PrimitiveID as an input to fragment shaders.
- - Added gl_FragCoord qualifiers origin_upper_left, and pixel_center_integer to modify the values returned by gl_FragCoord (and have no affect on any other aspect of the pipeline or language).
- - including redeclaration of gl_FragCoord that adds nothing
+ + Added gl_FragCoord qualifiers origin_upper_left, and pixel_center_integer to modify the values returned by gl_FragCoord (and have no affect on any other aspect of the pipeline or language).
+ + including redeclaration of gl_FragCoord that adds nothing
- Added support for multi-sample textures through sampler2DMS and sampler2DMSArray support in texelFetch() and textureSize().
+ Broadened interface blocks from just uniforms to in and out interfaces as well.
+ Broaden array usage to include vertex shader inputs (vertex in).
diff --git a/glslang.vcxproj b/glslang.vcxproj
index 47ba6ee..7685882 100644
--- a/glslang.vcxproj
+++ b/glslang.vcxproj
@@ -165,6 +165,7 @@
<ClCompile Include="glslang\MachineIndependent\preprocessor\PpContext.cpp" />
<ClCompile Include="glslang\MachineIndependent\preprocessor\PpSymbols.cpp" />
<ClCompile Include="glslang\MachineIndependent\preprocessor\PpTokens.cpp" />
+ <ClCompile Include="glslang\MachineIndependent\reflection.cpp" />
<ClCompile Include="glslang\MachineIndependent\Scan.cpp" />
<ClCompile Include="glslang\MachineIndependent\Versions.cpp" />
<ClCompile Include="OGLCompilersDLL\InitializeDll.cpp" />
@@ -192,6 +193,7 @@
<ClInclude Include="glslang\MachineIndependent\ParseHelper.h" />
<ClInclude Include="glslang\MachineIndependent\preprocessor\PpContext.h" />
<ClInclude Include="glslang\MachineIndependent\preprocessor\PpTokens.h" />
+ <ClInclude Include="glslang\MachineIndependent\reflection.h" />
<ClInclude Include="glslang\MachineIndependent\RemoveTree.h" />
<ClInclude Include="glslang\MachineIndependent\localintermediate.h" />
<ClInclude Include="glslang\Include\BaseTypes.h" />
diff --git a/glslang.vcxproj.filters b/glslang.vcxproj.filters
index 62fb9ae..2f3b2be 100644
--- a/glslang.vcxproj.filters
+++ b/glslang.vcxproj.filters
@@ -112,6 +112,9 @@
<ClCompile Include="glslang\MachineIndependent\linkValidate.cpp">
<Filter>Machine Independent</Filter>
</ClCompile>
+ <ClCompile Include="glslang\MachineIndependent\reflection.cpp">
+ <Filter>Machine Independent</Filter>
+ </ClCompile>
</ItemGroup>
<ItemGroup>
<ClInclude Include="glslang\MachineIndependent\Initialize.h">
@@ -192,6 +195,9 @@
<ClInclude Include="glslang\MachineIndependent\preprocessor\PpTokens.h">
<Filter>Machine Independent\Preprocessor</Filter>
</ClInclude>
+ <ClInclude Include="glslang\MachineIndependent\reflection.h">
+ <Filter>Machine Independent</Filter>
+ </ClInclude>
</ItemGroup>
<ItemGroup>
<CustomBuild Include="glslang\MachineIndependent\glslang.y">
diff --git a/glslang/Include/intermediate.h b/glslang/Include/intermediate.h
index f1c5487..135ca9f 100644
--- a/glslang/Include/intermediate.h
+++ b/glslang/Include/intermediate.h
@@ -588,7 +588,7 @@
TIntermSelection(TIntermTyped* cond, TIntermNode* trueB, TIntermNode* falseB, const TType& type) :
TIntermTyped(type), condition(cond), trueBlock(trueB), falseBlock(falseB) {}
virtual void traverse(TIntermTraverser*);
- virtual TIntermNode* getCondition() const { return condition; }
+ virtual TIntermTyped* getCondition() const { return condition; }
virtual TIntermNode* getTrueBlock() const { return trueBlock; }
virtual TIntermNode* getFalseBlock() const { return falseBlock; }
virtual TIntermSelection* getAsSelectionNode() { return this; }
@@ -624,6 +624,12 @@
// When using this, just fill in the methods for nodes you want visited.
// Return false from a pre-visit to skip visiting that node's subtree.
//
+// Explicitly set postVisit to true if you want post visiting, otherwise,
+// filled in methods will only be called at pre-visit time (before processing
+// the subtree).
+//
+// If you only want post-visits, explicitly turn off preVisit and turn on postVisit.
+//
class TIntermTraverser {
public:
POOL_ALLOCATOR_NEW_DELETE(GetThreadPoolAllocator())
diff --git a/glslang/MachineIndependent/Makefile b/glslang/MachineIndependent/Makefile
index fe3b8bd..1f62ede 100644
--- a/glslang/MachineIndependent/Makefile
+++ b/glslang/MachineIndependent/Makefile
@@ -11,7 +11,7 @@
OBJECTS= Initialize.o IntermTraverse.o \
Intermediate.o ParseHelper.o PoolAlloc.o \
RemoveTree.o ShaderLang.o intermOut.o parseConst.o SymbolTable.o \
- InfoSink.o Versions.o Constant.o Scan.o limits.o linkValidate.o
+ InfoSink.o Versions.o Constant.o Scan.o limits.o linkValidate.o reflection.o
SRCS= gen_glslang_tab.cpp Initialize.cpp IntermTraverse.cpp \
Intermediate.cpp ParseHelper.cpp PoolAlloc.cp \
@@ -147,3 +147,4 @@
Constant.o: localintermediate.h ../Include/intermediate.h ../Public/ShaderLang.h SymbolTable.h Versions.h
limits.o: ParseHelper.h
linkValidate.o: localintermediate.h
+reflection.o: ../Include/Common.h reflection.h localintermediate.h
diff --git a/glslang/MachineIndependent/ShaderLang.cpp b/glslang/MachineIndependent/ShaderLang.cpp
index ea93c15..2b22a8c 100644
--- a/glslang/MachineIndependent/ShaderLang.cpp
+++ b/glslang/MachineIndependent/ShaderLang.cpp
@@ -53,6 +53,7 @@
#define SH_EXPORTING
#include "../Public/ShaderLang.h"
+#include "reflection.h"
#include "Initialize.h"
namespace { // anonymous namespace for file-local functions and symbols
@@ -967,18 +968,23 @@
return infoSink->debug.c_str();
}
-TProgram::TProgram() : pool(0)
+TProgram::TProgram() : pool(0), reflection(0), linked(false)
{
infoSink = new TInfoSink;
- for (int s = 0; s < EShLangCount; ++s)
+ for (int s = 0; s < EShLangCount; ++s) {
intermediate[s] = 0;
+ newedIntermediate[s] = false;
+ }
}
TProgram::~TProgram()
{
delete infoSink;
+ delete reflection;
+
for (int s = 0; s < EShLangCount; ++s)
- delete intermediate[s];
+ if (newedIntermediate[s])
+ delete intermediate[s];
delete pool;
}
@@ -989,8 +995,12 @@
//
bool TProgram::link(EShMessages messages)
{
- bool error = false;
+ if (linked)
+ return false;
+ linked = true;
+ bool error = false;
+
pool = new TPoolAllocator();
SetThreadPoolAllocator(*pool);
@@ -1013,12 +1023,11 @@
// Be efficient for the common single compilation unit per stage case,
// reusing it's TIntermediate instead of merging into a new one.
//
- TIntermediate* merged;
if (stages[stage].size() == 1)
- merged = stages[stage].front()->intermediate;
+ intermediate[stage] = stages[stage].front()->intermediate;
else {
intermediate[stage] = new TIntermediate(stage);
- merged = intermediate[stage];
+ newedIntermediate[stage] = true;
}
infoSink->info << "\nLinked " << StageName(stage) << " stage:\n\n";
@@ -1026,15 +1035,15 @@
if (stages[stage].size() > 1) {
std::list<TShader*>::const_iterator it;
for (it = stages[stage].begin(); it != stages[stage].end(); ++it)
- merged->merge(*infoSink, *(*it)->intermediate);
+ intermediate[stage]->merge(*infoSink, *(*it)->intermediate);
if (messages & EShMsgAST)
- merged->outputTree(*infoSink);
+ intermediate[stage]->outputTree(*infoSink);
}
- merged->errorCheck(*infoSink);
+ intermediate[stage]->errorCheck(*infoSink);
- return merged->getNumErrors() > 0;
+ return intermediate[stage]->getNumErrors() > 0;
}
const char* TProgram::getInfoLog()
@@ -1047,4 +1056,38 @@
return infoSink->debug.c_str();
}
+//
+// Reflection implementation.
+//
+
+bool TProgram::buildReflection()
+{
+ if (! linked || reflection)
+ return false;
+
+ reflection = new TReflection;
+
+ for (int s = 0; s < EShLangCount; ++s) {
+ if (intermediate[s]) {
+ if (! reflection->addStage((EShLanguage)s, *intermediate[s]))
+ return false;
+ }
+ }
+
+ return true;
+}
+
+int TProgram::getNumLiveUniformVariables() { return reflection->getNumUniforms(); }
+int TProgram::getNumLiveUniformBlocks() { return reflection->getNumUniformBlocks(); }
+const char* TProgram::getUniformName(int index) { return reflection->getUniform(index).name.c_str(); }
+const char* TProgram::getUniformBlockName(int index) { return reflection->getUniformBlock(index).name.c_str(); }
+int TProgram::getUniformBlockSize(int index) { return reflection->getUniformBlock(index).size; }
+int TProgram::getUniformIndex(const char* name) { return reflection->getIndex(name); }
+int TProgram::getUniformBlockIndex(int index) { return reflection->getUniform(index).index; }
+int TProgram::getUniformType(int index) { return reflection->getUniform(index).glDefineType; }
+int TProgram::getUniformBufferOffset(int index) { return reflection->getUniform(index).offset; }
+int TProgram::getUniformArraySize(int index) { return reflection->getUniform(index).size; }
+
+void TProgram::dumpReflection() { reflection->dump(); }
+
} // end namespace glslang
diff --git a/glslang/MachineIndependent/linkValidate.cpp b/glslang/MachineIndependent/linkValidate.cpp
index 0c1fb7b..55bfdc6 100644
--- a/glslang/MachineIndependent/linkValidate.cpp
+++ b/glslang/MachineIndependent/linkValidate.cpp
@@ -296,7 +296,7 @@
TCall* call = stack.back();
// Add to the stack just one callee.
- // This algorithm always terminates, because only ! visited and ! currentPath causes a push
+ // This algorithm always terminates, because only !visited and !currentPath causes a push
// and all pushes change currentPath to true, and all pops change visited to true.
TGraph::iterator child = callGraph.begin();
for (; child != callGraph.end(); ++child) {
@@ -312,6 +312,7 @@
error(infoSink, "Recursion detected:");
infoSink.info << " " << call->callee << " calling " << child->callee << "\n";
child->errorGiven = true;
+ recursive = true;
}
} else {
child->currentPath = true;
diff --git a/glslang/MachineIndependent/localintermediate.h b/glslang/MachineIndependent/localintermediate.h
index 5657fca..0931c50 100644
--- a/glslang/MachineIndependent/localintermediate.h
+++ b/glslang/MachineIndependent/localintermediate.h
@@ -56,7 +56,8 @@
//
class TIntermediate {
public:
- explicit TIntermediate(EShLanguage l, int v = 0, EProfile p = ENoProfile) : language(l), treeRoot(0), profile(p), version(v), numMains(0), numErrors(0),
+ explicit TIntermediate(EShLanguage l, int v = 0, EProfile p = ENoProfile) : language(l), treeRoot(0), profile(p), version(v),
+ numMains(0), numErrors(0), recursive(false),
invocations(0), maxVertices(0), inputPrimitive(ElgNone), outputPrimitive(ElgNone), pixelCenterInteger(false), originUpperLeft(false) { }
void setVersion(int v) { version = v; }
@@ -66,7 +67,9 @@
void setTreeRoot(TIntermNode* r) { treeRoot = r; }
TIntermNode* getTreeRoot() const { return treeRoot; }
void addMainCount() { ++numMains; }
+ int getNumMains() const { return numMains; }
int getNumErrors() const { return numErrors; }
+ bool isRecursive() const { return recursive; }
TIntermSymbol* addSymbol(int Id, const TString&, const TType&, TSourceLoc);
TIntermTyped* addConversion(TOperator, const TType&, TIntermTyped*);
@@ -157,6 +160,7 @@
int version;
int numMains;
int numErrors;
+ bool recursive;
int invocations;
int maxVertices;
TLayoutGeometry inputPrimitive;
diff --git a/glslang/MachineIndependent/reflection.cpp b/glslang/MachineIndependent/reflection.cpp
new file mode 100644
index 0000000..44cd22c
--- /dev/null
+++ b/glslang/MachineIndependent/reflection.cpp
@@ -0,0 +1,333 @@
+//
+//Copyright (C) 2013 LunarG, Inc.
+//
+//All rights reserved.
+//
+//Redistribution and use in source and binary forms, with or without
+//modification, are permitted provided that the following conditions
+//are met:
+//
+// Redistributions of source code must retain the above copyright
+// notice, this list of conditions and the following disclaimer.
+//
+// Redistributions in binary form must reproduce the above
+// copyright notice, this list of conditions and the following
+// disclaimer in the documentation and/or other materials provided
+// with the distribution.
+//
+// Neither the name of 3Dlabs Inc. Ltd. nor the names of its
+// contributors may be used to endorse or promote products derived
+// from this software without specific prior written permission.
+//
+//THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+//"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+//LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
+//FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
+//COPYRIGHT HOLDERS OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
+//INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
+//BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
+//LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+//CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+//LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN
+//ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
+//POSSIBILITY OF SUCH DAMAGE.
+//
+
+#include "../Include/Common.h"
+#include "reflection.h"
+#include "localintermediate.h"
+
+//
+// Grow the reflection database through a friend traverser class of TReflection and a
+// collection of functions to do a liveness traversal that note what uniforms are used
+// in semantically non-dead code.
+//
+// Can be used multiple times, once per stage, to grow a program reflection.
+//
+// High-level algorithm for one stage:
+//
+// 1. Put main() on list of live functions.
+//
+// 2. Traverse any live function, while skipping if-tests with a compile-time constant
+// condition of false, and while adding any encountered function calls to the live
+// function list.
+//
+// Repeat until the live function list is empty.
+//
+// 3. Add any encountered uniform variables and blocks to the reflection database.
+//
+// Can be attempted with a failed link, but will return false if recursion had been detected, or
+// there wasn't exactly one main.
+//
+
+namespace glslang {
+
+//
+// The traverser: mostly pass through, except
+// - processing function-call nodes to push live functions onto the stack of functions to process
+// - processing binary nodes to see if they are dereferences of aggregates to track
+// - processing symbol nodes to see if they are non-aggregate objects to track
+// - processing selection nodes to trim semantically dead code
+//
+// This is in the glslang namespace directly so it can be a friend of TReflection.
+//
+
+class TLiveTraverser : public TIntermTraverser {
+public:
+ TLiveTraverser(const TIntermediate& i, TReflection& r) : intermediate(i), reflection(r) { }
+
+ // Track live funtions as well as uniforms, so that we don't visit dead functions
+ // and only visit each function once.
+ void addFunctionCall(TIntermAggregate* call)
+ {
+ // just use the map to ensure we process each function at most once
+ if (reflection.nameToIndex.find(call->getName()) == reflection.nameToIndex.end()) {
+ reflection.nameToIndex[call->getName()] = -1;
+ pushFunction(call->getName());
+ }
+ }
+
+ // Add a simple uniform variable reference to the uniform database, no derefence involved.
+ void addUniform(const TIntermSymbol& symbol)
+ {
+ if (reflection.nameToIndex.find(symbol.getName()) == reflection.nameToIndex.end()) {
+ if (isReflectionGranularity(symbol.getType())) {
+ reflection.nameToIndex[symbol.getName()] = reflection.indexToUniform.size();
+ reflection.indexToUniform.push_back(TObjectReflection(symbol.getName(), -1, MapToGlType(symbol.getType()), MapToGlArraySize(symbol.getType()), -1));
+ }
+ }
+ }
+
+ // Add a complex uniform reference where blocks/struct/arrays are involved in tha access.
+ void addDereferencedUniform(TIntermSymbol* base, TIntermBinary* node)
+ {
+ bool block = base->getBasicType() == EbtBlock;
+ int offset = -1;
+ int blockIndex = -1;
+ bool anonymous = false;
+ if (block) {
+ anonymous = base->getName().compare(0, 6, "__anon") == 0;
+ const TString& blockName = anonymous ? base->getType().getTypeName() : base->getName();
+ TReflection::TNameToIndex::const_iterator it = reflection.nameToIndex.find(blockName);
+ if (it == reflection.nameToIndex.end()) {
+ blockIndex = reflection.indexToUniformBlock.size();
+ reflection.nameToIndex[blockName] = blockIndex;
+ reflection.indexToUniformBlock.push_back(TObjectReflection(blockName, -1, -1, 1, -1));
+ } else
+ blockIndex = it->second;
+ }
+ TString name;
+
+ switch (node->getOp()) {
+ case EOpIndexDirect:
+ case EOpIndexIndirect:
+ // TODO: reflection: handle array dereferences
+ //name = base->getName();
+ //name.append("[]");
+ break;
+ case EOpIndexDirectStruct:
+ {
+ if (! anonymous) {
+ name = base->getName();
+ name.append(".");
+ }
+ int structIndex = node->getRight()->getAsConstantUnion()->getConstArray()[0].getIConst();
+ if (block)
+ offset = structIndex * 16; // TODO: reflection: compute std140 offsets
+ name.append((*base->getType().getStruct())[structIndex].type->getFieldName().c_str());
+ break;
+ }
+ default:
+ break;
+ }
+
+ // TODO: reflection: handle deeper dereference chains than just one dereference
+
+ if (name.size() > 0) {
+ if (reflection.nameToIndex.find(name) == reflection.nameToIndex.end()) {
+ reflection.nameToIndex[name] = reflection.indexToUniform.size();
+ reflection.indexToUniform.push_back(TObjectReflection(name, offset, MapToGlType(node->getType()), MapToGlArraySize(node->getType()), blockIndex));
+ }
+ }
+ }
+
+ //
+ // Given a function name, find its subroot in the tree, and push it onto the stack of
+ // functions left to process.
+ //
+ void pushFunction(const TString& name)
+ {
+ TIntermSequence& globals = intermediate.getTreeRoot()->getAsAggregate()->getSequence();
+ for (unsigned int f = 0; f < globals.size(); ++f) {
+ TIntermAggregate* candidate = globals[f]->getAsAggregate();
+ if (candidate && candidate->getOp() == EOpFunction && candidate->getName() == name) {
+ functions.push_back(candidate);
+ break;
+ }
+ }
+ }
+
+ // Are we at a level in a dereference chain at which individual active uniform queries are made?
+ bool isReflectionGranularity(const TType& type)
+ {
+ return type.getBasicType() != EbtBlock && type.getBasicType() != EbtStruct;
+ }
+
+ // For a binary operation indexing into an aggregate, chase down the base of the aggregate.
+ // Return 0 if the topology does not fit this situation.
+ TIntermSymbol* findBase(const TIntermBinary* node)
+ {
+ TIntermSymbol *symbol = node->getLeft()->getAsSymbolNode();
+ if (symbol)
+ return symbol;
+ TIntermBinary* left = node->getLeft()->getAsBinaryNode();
+ if (! left)
+ return 0;
+
+ return findBase(left);
+ }
+
+ int MapToGlType(const TType& type)
+ {
+ // TODO: reflection: flesh out all GL types
+ #define GL_FLOAT_VEC4 0x8B52
+
+ return GL_FLOAT_VEC4;
+ }
+
+ int MapToGlArraySize(const TType& type)
+ {
+ return type.isArray() ? type.getArraySize() : 1;
+ }
+
+ typedef std::list<TIntermAggregate*> TFunctionStack;
+ TFunctionStack functions;
+ const TIntermediate& intermediate;
+ TReflection& reflection;
+};
+
+namespace {
+
+//
+// Implement the traversal functions of interest.
+//
+
+// To catch which function calls are not dead, and hence which functions must be visited.
+bool LiveAggregate(bool /* preVisit */, TIntermAggregate* node, TIntermTraverser* it)
+{
+ TLiveTraverser* oit = static_cast<TLiveTraverser*>(it);
+
+ if (node->getOp() == EOpFunctionCall)
+ oit->addFunctionCall(node);
+
+ return true; // traverse this subtree
+}
+
+// To catch dereferenced aggregates that must be reflected.
+bool LiveBinary(bool /* preVisit */, TIntermBinary* node, TIntermTraverser* it)
+{
+ TLiveTraverser* oit = static_cast<TLiveTraverser*>(it);
+
+ switch (node->getOp()) {
+ case EOpIndexDirect:
+ case EOpIndexIndirect:
+ case EOpIndexDirectStruct:
+ // If the left side is already small enough granularity to report, ignore
+ // this operation, and pick it up when the left side is visited.
+ if (! oit->isReflectionGranularity(node->getLeft()->getType()) &&
+ oit->isReflectionGranularity(node->getType())) {
+ // right granularity; see if this really is a uniform-based dereference
+ TIntermSymbol* base = oit->findBase(node);
+ if (base && base->getQualifier().storage == EvqUniform)
+ oit->addDereferencedUniform(base, node);
+ }
+ default:
+ break;
+ }
+
+ return true; // still need to visit everything below
+}
+
+// To catch non-dereferenced objects that must be reflected.
+void LiveSymbol(TIntermSymbol* symbol, TIntermTraverser* it)
+{
+ TLiveTraverser* oit = static_cast<TLiveTraverser*>(it);
+
+ if (symbol->getQualifier().storage == EvqUniform)
+ oit->addUniform(*symbol);
+}
+
+// To prune semantically dead paths.
+bool LiveSelection(bool /* preVisit */, TIntermSelection* node, TIntermTraverser* it)
+{
+ TLiveTraverser* oit = static_cast<TLiveTraverser*>(it);
+
+ TIntermConstantUnion* constant = node->getCondition()->getAsConstantUnion();
+ if (constant) {
+ // cull the path that is dead
+ if (constant->getConstArray()[0].getBConst() == true && node->getTrueBlock())
+ node->getTrueBlock()->traverse(it);
+ if (constant->getConstArray()[0].getBConst() == false && node->getFalseBlock())
+ node->getFalseBlock()->traverse(it);
+
+ return false; // don't traverse any more, we did it all above
+ } else
+ return true; // traverse the whole subtree
+}
+
+} // end anonymous namespace
+
+//
+// Implement TReflection methods.
+//
+
+// Merge live symbols from 'intermediate' into the existing reflection database.
+//
+// Returns false if the input is too malformed to do this.
+bool TReflection::addStage(EShLanguage, const TIntermediate& intermediate)
+{
+ if (intermediate.getNumMains() != 1 || intermediate.isRecursive())
+ return false;
+
+ TLiveTraverser it(intermediate, *this);
+ it.visitSymbol = LiveSymbol;
+ it.visitSelection = LiveSelection;
+ it.visitBinary = LiveBinary;
+ it.visitAggregate = LiveAggregate;
+
+ // put main() on functions to process
+ it.pushFunction("main(");
+
+ // process all the functions
+ while (! it.functions.empty()) {
+ TIntermNode* function = it.functions.back();
+ it.functions.pop_back();
+ function->traverse(&it);
+ }
+
+ return true;
+}
+
+void TReflection::dump()
+{
+ printf("Uniform reflection:\n");
+ for (size_t i = 0; i < indexToUniform.size(); ++i) {
+ printf("%d:", i);
+ indexToUniform[i].dump();
+ }
+ printf("\n");
+
+ printf("Uniform block reflection:\n");
+ for (size_t i = 0; i < indexToUniformBlock.size(); ++i) {
+ printf("%d: ", i);
+ indexToUniformBlock[i].dump();
+ }
+ printf("\n");
+
+ printf("Live names\n");
+ for (TNameToIndex::const_iterator it = nameToIndex.begin(); it != nameToIndex.end(); ++it)
+ printf("%s: %d\n", it->first.c_str(), it->second);
+ printf("\n");
+}
+
+} // end namespace glslang
diff --git a/glslang/MachineIndependent/reflection.h b/glslang/MachineIndependent/reflection.h
new file mode 100644
index 0000000..efff377
--- /dev/null
+++ b/glslang/MachineIndependent/reflection.h
@@ -0,0 +1,122 @@
+//
+//Copyright (C) 2013 LunarG, Inc.
+//
+//All rights reserved.
+//
+//Redistribution and use in source and binary forms, with or without
+//modification, are permitted provided that the following conditions
+//are met:
+//
+// Redistributions of source code must retain the above copyright
+// notice, this list of conditions and the following disclaimer.
+//
+// Redistributions in binary form must reproduce the above
+// copyright notice, this list of conditions and the following
+// disclaimer in the documentation and/or other materials provided
+// with the distribution.
+//
+// Neither the name of 3Dlabs Inc. Ltd. nor the names of its
+// contributors may be used to endorse or promote products derived
+// from this software without specific prior written permission.
+//
+//THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+//"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+//LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
+//FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
+//COPYRIGHT HOLDERS OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
+//INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
+//BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
+//LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+//CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+//LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN
+//ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
+//POSSIBILITY OF SUCH DAMAGE.
+//
+
+#ifndef _REFLECTION_INCLUDED
+#define _REFLECTION_INCLUDED
+
+#include "../Public/ShaderLang.h"
+
+#include <list>
+#include <set>
+
+//
+// A reflection database and its interface, consistent with the OpenGL API reflection queries.
+//
+
+namespace glslang {
+
+class TIntermediate;
+class TIntermAggregate;
+class TLiveTraverser;
+
+// Data needed for just a single object at the granularity exchanged by the reflection API
+class TObjectReflection {
+public:
+ TObjectReflection(const TString pName, int pOffset, int pGLDefineType, int pSize, int pIndex) :
+ name(pName), offset(pOffset), glDefineType(pGLDefineType), size(pSize), index(pIndex) { }
+ void dump() const { printf("%s: offset %d, type %d, arraySize %d, index %d\n", name.c_str(), offset, glDefineType, size, index); }
+ const TString name;
+ int offset;
+ int glDefineType;
+ int size; // data size in bytes for a block, array size for a (non-block) object that's an array
+ int index;
+};
+
+// The full reflection database
+class TReflection {
+public:
+ TReflection() : badReflection("__bad__", -1, -1, -1, -1) {}
+ virtual ~TReflection() {}
+
+ // grow the reflection stage by stage
+ bool addStage(EShLanguage, const TIntermediate&);
+
+ // for mapping a uniform index to a uniform object's description
+ int getNumUniforms() { return indexToUniform.size(); }
+ const TObjectReflection& getUniform(int i) const
+ {
+ if (i >= 0 && i < (int)indexToUniform.size())
+ return indexToUniform[i];
+ else
+ return badReflection;
+ }
+
+ // for mapping a block index to the block's description
+ int getNumUniformBlocks() const { return indexToUniformBlock.size(); }
+ const TObjectReflection& getUniformBlock(int i) const
+ {
+ if (i >= 0 && i < (int)indexToUniformBlock.size())
+ return indexToUniformBlock[i];
+ else
+ return badReflection;
+ }
+
+ // for mapping any name to its index (both block names and uniforms names)
+ int getIndex(const char* name) const
+ {
+ TNameToIndex::const_iterator it = nameToIndex.find(name);
+ if (it == nameToIndex.end())
+ return -1;
+ else
+ return it->second;
+ }
+
+ void dump();
+
+protected:
+ friend glslang::TLiveTraverser;
+
+ typedef std::map<TString, int> TNameToIndex;
+ typedef std::vector<TObjectReflection> TMapIndexToReflection;
+
+ TObjectReflection badReflection; // return for queries of -1 or generally out of range; has expected descriptions with in it for this
+ TNameToIndex nameToIndex; // maps names to indexes; can hold all types of data: uniform/buffer and which function names have been processed
+ TMapIndexToReflection indexToUniform;
+ TMapIndexToReflection indexToUniformBlock;
+};
+
+} // end namespace glslang
+
+#endif _REFLECTION_INCLUDED
diff --git a/glslang/Public/ShaderLang.h b/glslang/Public/ShaderLang.h
index f9d3336..0ab620f 100644
--- a/glslang/Public/ShaderLang.h
+++ b/glslang/Public/ShaderLang.h
@@ -305,6 +305,8 @@
TShader& operator=(TShader&);
};
+class TReflection;
+
// Make one TProgram per set of shaders that will get linked together. Add all
// the shaders that are to be linked together. After calling shader.parse()
// for all shaders, call link().
@@ -316,17 +318,36 @@
TProgram();
virtual ~TProgram();
void addShader(TShader* shader) { stages[shader->stage].push_back(shader); }
+
+ // Link Validation interface
bool link(EShMessages);
const char* getInfoLog();
const char* getInfoDebugLog();
+
+ // Reflection Interface
+ bool buildReflection(); // call first, to do liveness analysis, index mapping, etc.; returns false on failure
+ int getNumLiveUniformVariables(); // can be used for glGetProgramiv(GL_ACTIVE_UNIFORMS)
+ int getNumLiveUniformBlocks(); // can be used for glGetProgramiv(GL_ACTIVE_UNIFORM_BLOCKS)
+ const char* getUniformName(int index); // can be used for "name" part of glGetActiveUniform()
+ const char* getUniformBlockName(int index); // can be used for glGetActiveUniformBlockName()
+ int getUniformBlockSize(int index); // can be used for glGetActiveUniformBlockiv(UNIFORM_BLOCK_DATA_SIZE)
+ int getUniformIndex(const char* name); // can be used for glGetUniformIndices()
+ int getUniformBlockIndex(int index); // can be used for glGetActiveUniformsiv(GL_UNIFORM_BLOCK_INDEX)
+ int getUniformType(int index); // can be used for glGetActiveUniformsiv(GL_UNIFORM_TYPE)
+ int getUniformBufferOffset(int index); // can be used for glGetActiveUniformsiv(GL_UNIFORM_OFFSET)
+ int getUniformArraySize(int index); // can be used for glGetActiveUniformsiv(GL_UNIFORM_SIZE)
+ void dumpReflection();
+
protected:
bool linkStage(EShLanguage, EShMessages);
-protected:
TPoolAllocator* pool;
std::list<TShader*> stages[EShLangCount];
TIntermediate* intermediate[EShLangCount];
+ bool newedIntermediate[EShLangCount]; // track which intermediate were "new" versus reusing a singleton unit in a stage
TInfoSink* infoSink;
+ TReflection* reflection;
+ bool linked;
private:
TProgram& operator=(TProgram&);