diff --git a/StandAlone/StandAlone.cpp b/StandAlone/StandAlone.cpp index b3a37dc4..a3c1fafa 100644 --- a/StandAlone/StandAlone.cpp +++ b/StandAlone/StandAlone.cpp @@ -61,6 +61,7 @@ enum TOptions { EOptionsLinkProgram = 0x020, EOptionMultiThreaded = 0x040, EOptionDumpConfig = 0x080, + EOptionDumpReflection = 0x100, }; // @@ -466,6 +467,9 @@ bool ProcessArguments(int argc, char* argv[]) case 'm': Options |= EOptionMemoryLeakMode; break; + case 'q': + Options |= EOptionDumpReflection; + break; case 'r': Options |= EOptionRelaxedErrors; break; @@ -575,6 +579,11 @@ void CompileAndLinkShaders() 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 @@ void usage() "-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 00000000..dd02aead --- /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 00000000..79a6ef84 --- /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 5f0e9411..92633042 100644 --- a/Test/runtests +++ b/Test/runtests @@ -43,6 +43,13 @@ runLinkTest 300link2.frag runLinkTest 300link3.frag 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 # diff --git a/Todo.txt b/Todo.txt index 29ed8c1c..7aff9fe3 100644 --- a/Todo.txt +++ b/Todo.txt @@ -36,6 +36,8 @@ Link Validation - 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 @@ Shader Functionality to Implement/Finish + 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 47ba6ee1..76858826 100644 --- a/glslang.vcxproj +++ b/glslang.vcxproj @@ -165,6 +165,7 @@ xcopy /y $(IntDir)$(TargetName)$(TargetExt) Test + @@ -192,6 +193,7 @@ xcopy /y $(IntDir)$(TargetName)$(TargetExt) Test + diff --git a/glslang.vcxproj.filters b/glslang.vcxproj.filters index 62fb9ae2..2f3b2be5 100644 --- a/glslang.vcxproj.filters +++ b/glslang.vcxproj.filters @@ -112,6 +112,9 @@ Machine Independent + + Machine Independent + @@ -192,6 +195,9 @@ Machine Independent\Preprocessor + + Machine Independent + diff --git a/glslang/Include/intermediate.h b/glslang/Include/intermediate.h index f1c54873..135ca9f6 100644 --- a/glslang/Include/intermediate.h +++ b/glslang/Include/intermediate.h @@ -588,7 +588,7 @@ public: 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 @@ protected: // 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 fe3b8bd8..1f62edea 100644 --- a/glslang/MachineIndependent/Makefile +++ b/glslang/MachineIndependent/Makefile @@ -11,7 +11,7 @@ LIBCODEGEN=./../GenericCodeGen/libCodeGen.a 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 @@ Versions.o: ParseHelper.h Versions.h ../Include/ShHandle.h SymbolTable.h locali 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 ea93c151..2b22a8c7 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 @@ const char* TShader::getInfoDebugLog() 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 @@ TProgram::~TProgram() // 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 @@ bool TProgram::linkStage(EShLanguage stage, EShMessages messages) // 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 @@ bool TProgram::linkStage(EShLanguage stage, EShMessages messages) if (stages[stage].size() > 1) { std::list::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 @@ const char* TProgram::getInfoDebugLog() 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 0c1fb7bb..55bfdc63 100644 --- a/glslang/MachineIndependent/linkValidate.cpp +++ b/glslang/MachineIndependent/linkValidate.cpp @@ -296,7 +296,7 @@ void TIntermediate::checkCallGraphCycles(TInfoSink& infoSink) 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 @@ void TIntermediate::checkCallGraphCycles(TInfoSink& infoSink) 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 5657fca1..0931c505 100644 --- a/glslang/MachineIndependent/localintermediate.h +++ b/glslang/MachineIndependent/localintermediate.h @@ -56,7 +56,8 @@ class TSymbol; // 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 @@ public: 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 @@ protected: 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 00000000..44cd22c9 --- /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 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(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(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(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(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 00000000..efff3776 --- /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 +#include + +// +// 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 TNameToIndex; + typedef std::vector 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 f9d33364..0ab620fd 100644 --- a/glslang/Public/ShaderLang.h +++ b/glslang/Public/ShaderLang.h @@ -305,6 +305,8 @@ private: 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 @@ public: 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 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&);