Add link validation infrastructure for multiple compilation units per stage. Include...
authorJohn Kessenich <cepheus@frii.com>
Wed, 4 Sep 2013 21:19:27 +0000 (21:19 +0000)
committerJohn Kessenich <cepheus@frii.com>
Wed, 4 Sep 2013 21:19:27 +0000 (21:19 +0000)
git-svn-id: https://cvs.khronos.org/svn/repos/ogl/trunk/ecosystem/public/sdk/tools/glslang@22927 e7fa87d3-cd2b-0410-9028-fcbf551c1848

24 files changed:
StandAlone/StandAlone.cpp
Test/baseResults/cppComplexExpr.vert.out
Test/baseResults/errors.frag.out
Test/baseResults/mains1.frag.out [new file with mode: 0644]
Test/baseResults/noMain.vert.out [new file with mode: 0644]
Test/baseResults/pointCoord.frag.out
Test/baseResults/versionsClean.frag.out
Test/baseResults/versionsErrors.frag.out
Test/mains.frag [new file with mode: 0644]
Test/mains1.frag [new file with mode: 0644]
Test/mains2.frag [new file with mode: 0644]
Test/noMain.vert [new file with mode: 0644]
Test/noMain1.geom [new file with mode: 0644]
Test/noMain2.geom [new file with mode: 0644]
Test/runtests
glslang/Include/ShHandle.h
glslang/MachineIndependent/Initialize.cpp
glslang/MachineIndependent/Intermediate.cpp
glslang/MachineIndependent/ParseHelper.cpp
glslang/MachineIndependent/ParseHelper.h
glslang/MachineIndependent/ShaderLang.cpp
glslang/MachineIndependent/Versions.cpp
glslang/MachineIndependent/localintermediate.h
glslang/Public/ShaderLang.h

index 07e5257..3b26488 100644 (file)
@@ -161,18 +161,16 @@ bool ProcessArguments(int argc, char* argv[])
     return true;
 }
 
-// Thread entry point
+// Thread entry point, for non-linking asynchronous mode.
 unsigned int
 #ifdef _WIN32
     __stdcall
 #endif
 CompileShaders(void*)
 {
-    ShHandle compiler;
-
     std::string shaderName;
     while (Worklist.remove(shaderName)) {
-        compiler = ShConstructCompiler(FindLanguage(shaderName), Options);
+        ShHandle compiler = ShConstructCompiler(FindLanguage(shaderName), Options);
         if (compiler == 0)
             return false;
 
@@ -189,6 +187,77 @@ CompileShaders(void*)
     return 0;
 }
 
+//
+// For linking mode: Will independently parse each item in the worklist, but then put them
+// in the same program and link them together.
+//
+// Uses the new C++ interface instead of the old handle-based interface.
+//
+void CompileAndLinkShaders()
+{
+    // keep track of what to free
+    std::list<glslang::TShader*> shaders;
+
+    EShMessages messages = EShMsgDefault;
+    if (Options & EOptionRelaxedErrors)
+        messages = (EShMessages)(messages | EShMsgRelaxedErrors);
+    if (Options & EOptionIntermediate)
+        messages = (EShMessages)(messages | EShMsgAST);
+
+    TBuiltInResource resources;
+    GenerateResources(resources);
+
+    //
+    // Per-shader processing...
+    //
+
+    glslang::TProgram program;
+    std::string shaderName;
+    while (Worklist.remove(shaderName)) {
+        EShLanguage stage = FindLanguage(shaderName);
+        glslang::TShader* shader = new glslang::TShader(stage);
+        shaders.push_back(shader);
+    
+        char** shaderStrings = ReadFileData(shaderName.c_str());
+        if (! shaderStrings) {
+            usage();
+            return;
+        }
+
+        shader->setStrings(shaderStrings, 1);
+
+        shader->parse(&resources, 100, false, messages);
+        
+        program.addShader(shader);
+
+        if (! (Options & EOptionSuppressInfolog)) {
+            puts(shaderName.c_str());
+            puts(shader->getInfoLog());
+            puts(shader->getInfoDebugLog());
+        }
+
+        FreeFileData(shaderStrings);
+    }
+
+    //
+    // Program-level processing...
+    //
+
+    program.link(messages);
+    if (! (Options & EOptionSuppressInfolog)) {
+        puts(program.getInfoLog());
+        puts(program.getInfoDebugLog());
+    }
+
+    // free everything up
+    while (shaders.size() > 0) {
+        delete shaders.back();
+        shaders.pop_back();
+    }
+
+    // TODO: memory: for each compile, need a GetThreadPoolAllocator().pop();
+}
+
 int C_DECL main(int argc, char* argv[])
 {
     bool compileFailed = false;
@@ -205,6 +274,12 @@ int C_DECL main(int argc, char* argv[])
         return EFailUsage;
     }
 
+    //
+    // Two modes:
+    // 1) linking all arguments together, single-threaded
+    // 2) independent arguments, can be tackled by multiple asynchronous threads, for testing thread safety
+    //
+
     // TODO: finish threading, allow external control over number of threads
     const int NumThreads = 1;
     if (NumThreads > 1) {
@@ -218,8 +293,12 @@ int C_DECL main(int argc, char* argv[])
         }
         glslang::OS_WaitForAllThreads(threads, NumThreads);
     } else {
-        if (! CompileShaders(0))
-            compileFailed = true;
+        if (Options & EOptionsLinkProgram) {
+            CompileAndLinkShaders();
+        } else {
+            if (! CompileShaders(0))
+                compileFailed = true;
+        }
     }
 
     if (Delay)
@@ -271,9 +350,10 @@ EShLanguage FindLanguage(const std::string& name)
 }
 
 //
-//   Read a file's data into a string, and compile it using ShCompile
+// Read a file's data into a string, and compile it using the old interface ShCompile, 
+// for non-linkable results.
 //
-bool CompileFile(const char *fileName, ShHandle compiler, int Options, const TBuiltInResource *resources)
+bool CompileFile(const char *fileName, ShHandle compiler, int Options, const TBuiltInResourceresources)
 {
     int ret;
     char** shaderStrings = ReadFileData(fileName);
@@ -296,6 +376,7 @@ bool CompileFile(const char *fileName, ShHandle compiler, int Options, const TBu
         messages = (EShMessages)(messages | EShMsgRelaxedErrors);
     if (Options & EOptionIntermediate)
         messages = (EShMessages)(messages | EShMsgAST);
+    
     for (int i = 0; i < ((Options & EOptionMemoryLeakMode) ? 100 : 1); ++i) {
         for (int j = 0; j < ((Options & EOptionMemoryLeakMode) ? 100 : 1); ++j) {
             //ret = ShCompile(compiler, shaderStrings, NumShaderStrings, lengths, EShOptNone, resources, Options, 100, false, messages);
@@ -315,7 +396,6 @@ bool CompileFile(const char *fileName, ShHandle compiler, int Options, const TBu
     return ret ? true : false;
 }
 
-
 //
 //   print usage to stdout
 //
@@ -429,16 +509,12 @@ char** ReadFileData(const char *fileName)
     return return_data;
 }
 
-
-
 void FreeFileData(char **data)
 {
     for(int i=0;i<NumShaderStrings;i++)
         free(data[i]);
 }
 
-
-
 void InfoLogMsg(const char* msg, const char* name, const int num)
 {
     printf(num >= 0 ? "#### %s %s %d INFO LOG ####\n" :
index 48ae895..36fb057 100644 (file)
@@ -1,4 +1,4 @@
-WARNING: 0:1: '#version' : statement missing: use #version on first line of shader \r
+WARNING: #version: statement missing; use #version on first line of shader\r
 0:? Sequence\r
 0:4  Sequence\r
 0:4    move second child to first child (highp float)\r
index 73d991b..279ffd7 100644 (file)
@@ -1,4 +1,4 @@
-WARNING: 0:1: '#version' : statement missing: use #version on first line of shader \r
+WARNING: #version: statement missing; use #version on first line of shader\r
 ERROR: 0:1: 'main' : function cannot take any parameter(s) \r
 ERROR: 0:1: 'int' :  main function cannot return a value\r
 ERROR: 2 compilation errors.  No code generated.\r
diff --git a/Test/baseResults/mains1.frag.out b/Test/baseResults/mains1.frag.out
new file mode 100644 (file)
index 0000000..69a2078
--- /dev/null
@@ -0,0 +1,33 @@
+mains1.frag\r
+\r
+0:3Function Definition: main( (void)\r
+0:3  Function Parameters: \r
+\r
+mains2.frag\r
+\r
+0:3Function Definition: main( (void)\r
+0:3  Function Parameters: \r
+\r
+noMain1.geom\r
+ERROR: #version: geometry shaders require non-es profile and version 150 or above\r
+ERROR: 1 compilation errors.  No code generated.\r
+\r
+\r
+0:3Function Definition: foo( (void)\r
+0:3  Function Parameters: \r
+\r
+noMain2.geom\r
+\r
+0:3Function Definition: bar( (void)\r
+0:3  Function Parameters: \r
+\r
+\r
+Linked geometry stage:\r
+\r
+ERROR: Missing entry point: Each stage requires one "void main()" entry point\r
+\r
+Linked fragment stage:\r
+\r
+ERROR: Too many entry points: Each stage can have at most one "void main()" entry point.\r
+\r
+\r
diff --git a/Test/baseResults/noMain.vert.out b/Test/baseResults/noMain.vert.out
new file mode 100644 (file)
index 0000000..f55806c
--- /dev/null
@@ -0,0 +1,26 @@
+noMain.vert\r
+\r
+0:3Function Definition: foo( (void)\r
+0:3  Function Parameters: \r
+\r
+mains.frag\r
+ERROR: 0:7: 'main' : function already has a body \r
+ERROR: 1 compilation errors.  No code generated.\r
+\r
+\r
+ERROR: node is still EOpNull!\r
+0:3  Function Definition: main( (void)\r
+0:3    Function Parameters: \r
+0:7  Function Definition: main( (void)\r
+0:7    Function Parameters: \r
+\r
+\r
+Linked vertex stage:\r
+\r
+ERROR: Missing entry point: Each stage requires one "void main()" entry point\r
+\r
+Linked fragment stage:\r
+\r
+ERROR: Too many entry points: Each stage can have at most one "void main()" entry point.\r
+\r
+\r
index 5b01501..94cef16 100644 (file)
@@ -1,4 +1,4 @@
-WARNING: 0:1: '#version' : statement missing: use #version on first line of shader \r
+WARNING: #version: statement missing; use #version on first line of shader\r
 0:? Sequence\r
 0:5  Function Definition: main( (void)\r
 0:5    Function Parameters: \r
index 821da67..8014ce5 100644 (file)
@@ -1,4 +1,4 @@
-ERROR: 0:1: '#version' : statement must appear first in ESSL shader; before comments or newlines \r
+ERROR: #version: statement must appear first in es-profile shader; before comments or newlines\r
 ERROR: 1 compilation errors.  No code generated.\r
 \r
 ERROR: node is still EOpNull!\r
index b13c08e..4110560 100644 (file)
@@ -1,5 +1,4 @@
 ERROR: #version: versions before 150 do not allow a profile token\r
-ERROR: 0:1: '#version' : incorrect \r
 ERROR: 0:38: 'attribute' : not supported in this stage: fragment\r
 ERROR: 0:40: 'sampler2DRect' : Reserved word. \r
 ERROR: 0:40: 'rectangle texture' : not supported for this version or the enabled extensions \r
diff --git a/Test/mains.frag b/Test/mains.frag
new file mode 100644 (file)
index 0000000..5756a3e
--- /dev/null
@@ -0,0 +1,9 @@
+#version 300 es\r
+\r
+void main()\r
+{\r
+}\r
+\r
+void main()\r
+{\r
+}\r
diff --git a/Test/mains1.frag b/Test/mains1.frag
new file mode 100644 (file)
index 0000000..e0de2e1
--- /dev/null
@@ -0,0 +1,5 @@
+#version 110\r
+\r
+void main()\r
+{\r
+}\r
diff --git a/Test/mains2.frag b/Test/mains2.frag
new file mode 100644 (file)
index 0000000..e0de2e1
--- /dev/null
@@ -0,0 +1,5 @@
+#version 110\r
+\r
+void main()\r
+{\r
+}\r
diff --git a/Test/noMain.vert b/Test/noMain.vert
new file mode 100644 (file)
index 0000000..e83be04
--- /dev/null
@@ -0,0 +1,5 @@
+#version 300 es\r
+\r
+void foo()\r
+{\r
+}\r
diff --git a/Test/noMain1.geom b/Test/noMain1.geom
new file mode 100644 (file)
index 0000000..8669845
--- /dev/null
@@ -0,0 +1,5 @@
+#version 110\r
+\r
+void foo()\r
+{\r
+}\r
diff --git a/Test/noMain2.geom b/Test/noMain2.geom
new file mode 100644 (file)
index 0000000..45c866b
--- /dev/null
@@ -0,0 +1,5 @@
+#version 150\r
+\r
+void bar()\r
+{\r
+}\r
index dae11f8..dca678f 100644 (file)
@@ -2,10 +2,28 @@
 
 TARGETDIR=localResults
 BASEDIR=baseResults
+EXE=./glslangValidator.exe
+
+#
+# isolated compilation tests
+#
 
 while read t; do
     echo Running $t...
-       b=`basename $t`
-    ./glslangValidator.exe -i $t > $TARGETDIR/$b.out
+    b=`basename $t`
+    $EXE -i $t > $TARGETDIR/$b.out
     diff -b $BASEDIR/$b.out $TARGETDIR/$b.out
 done < testlist
+
+#
+# grouped shaders for link tests
+#
+
+function runLinkTest {
+    echo Running $*...
+    $EXE -i -l $* > $TARGETDIR/$1.out
+       diff -b $BASEDIR/$1.out $TARGETDIR/$1.out
+}
+
+runLinkTest mains1.frag mains2.frag noMain1.geom noMain2.geom
+runLinkTest noMain.vert mains.frag 
index 659dbec..5e61a98 100644 (file)
@@ -103,7 +103,7 @@ protected:
 };
 
 //
-// Link operations are base on a list of compile results...
+// Link operations are based on a list of compile results...
 //
 typedef glslang::TVector<TCompiler*> TCompilerList;
 typedef glslang::TVector<TShHandleBase*> THandleList;
index 16ccd96..6e65161 100644 (file)
@@ -1429,7 +1429,7 @@ void TBuiltIns::initialize(const TBuiltInResource &resources, int version, EProf
                 s.append("uniform gl_LightProducts gl_BackLightProduct[gl_MaxLights];");
 
                 //
-                // Textureg Environment and Generation, p. 152, p. 40-42.
+                // Texture Environment and Generation, p. 152, p. 40-42.
                 //
                 s.append("uniform vec4  gl_TextureEnvColor[gl_MaxTextureImageUnits];");
                 s.append("uniform vec4  gl_EyePlaneS[gl_MaxTextureCoords];");
index 4d839c3..9baa293 100644 (file)
@@ -910,6 +910,30 @@ void TIntermediate::addSymbolLinkageNode(TIntermAggregate*& linkage, const TVari
 }
 
 //
+// Merge the information in 'unit' into 'this'
+//
+void TIntermediate::merge(TIntermediate& unit)
+{
+    numMains += unit.numMains;
+}
+
+void TIntermediate::errorCheck(TInfoSink& infoSink)
+{   
+    if (numMains < 1)
+        error(infoSink, "Missing entry point: Each stage requires one \"void main()\" entry point");
+    if (numMains > 1)
+        error(infoSink, "Too many entry points: Each stage can have at most one \"void main()\" entry point.");
+}
+
+void TIntermediate::error(TInfoSink& infoSink, const char* message)
+{
+    infoSink.info.prefix(EPrefixError);
+    infoSink.info << message << "\n";
+
+    ++numErrors;
+}
+
+//
 // This deletes the tree.
 //
 void TIntermediate::removeTree()
index e6599e0..0fe7908 100644 (file)
@@ -692,6 +692,7 @@ TIntermAggregate* TParseContext::handleFunctionPrototype(TSourceLoc loc, TFuncti
             error(loc, "function cannot take any parameter(s)", function.getName().c_str(), "");
         if (function.getReturnType().getBasicType() != EbtVoid)
             error(loc, "", function.getReturnType().getCompleteTypeString().c_str(), "main function cannot return a value");
+        intermediate.addMainCount();
     }
 
     //
index 1d404e9..f077ce6 100644 (file)
@@ -164,6 +164,7 @@ public:
     TScanContext* getScanContext() const { return scanContext; }
     void setPpContext(TPpContext* c) { ppContext = c; }
     TPpContext* getPpContext() const { return ppContext; }
+    void addError() { ++numErrors; }
     int getNumErrors() const { return numErrors; }
 
 protected:
index ece846b..fa2117a 100644 (file)
@@ -103,7 +103,7 @@ TPoolAllocator* PerProcessGPA = 0;
 bool InitializeSymbolTable(const TString& builtIns, int version, EProfile profile, EShLanguage language, TInfoSink& infoSink, 
                            TSymbolTable& symbolTable)
 {
-    TIntermediate intermediate(version, profile);      
+    TIntermediate intermediate(version, profile);
        
     TParseContext parseContext(symbolTable, intermediate, true, version, profile, language, infoSink);
     TPpContext ppContext(parseContext);
@@ -262,16 +262,23 @@ void SetupBuiltinSymbolTable(int version, EProfile profile)
     glslang::ReleaseGlobalLock();
 }
 
-bool DeduceProfile(TInfoSink& infoSink, int version, EProfile& profile)
+bool DeduceVersionProfile(TInfoSink& infoSink, EShLanguage stage, bool versionNotFirst, int defaultVersion, int& version, EProfile& profile)
 {
     const int FirstProfileVersion = 150;
+    bool correct = true;
+
+    // Get a good version...
+    if (version == 0) {
+        version = defaultVersion;
+        infoSink.info.message(EPrefixWarning, "#version: statement missing; use #version on first line of shader");
+    }
 
+    // Get a good profile...
     if (profile == ENoProfile) {
         if (version == 300) {
+            correct = false;
             infoSink.info.message(EPrefixError, "#version: version 300 requires specifying the 'es' profile");
             profile = EEsProfile;
-            
-            return false;
         } else if (version == 100)
             profile = EEsProfile;
         else if (version >= FirstProfileVersion)
@@ -281,39 +288,192 @@ bool DeduceProfile(TInfoSink& infoSink, int version, EProfile& profile)
     } else {
         // a profile was provided...
         if (version < 150) {
+            correct = false;
             infoSink.info.message(EPrefixError, "#version: versions before 150 do not allow a profile token");
             if (version == 100)
                 profile = EEsProfile;
             else
                 profile = ENoProfile;
-
-            return false;
         } else if (version == 300) {
             if (profile != EEsProfile) {
+                correct = false;
                 infoSink.info.message(EPrefixError, "#version: version 300 supports only the es profile");
-
-                return false;
             }
             profile = EEsProfile;
         } else {
             if (profile == EEsProfile) {
+                correct = false;
                 infoSink.info.message(EPrefixError, "#version: only version 300 supports the es profile");
                 if (version >= FirstProfileVersion)
                     profile = ECoreProfile;
                 else
                     profile = ENoProfile;
-
-                return false;
             } 
             // else: typical desktop case... e.g., "#version 410 core"
         }
     }
 
-    return true;
+    // Correct for stage type...
+    switch (stage) {
+    case EShLangGeometry:
+        if (version < 150 || profile == EEsProfile) {
+            correct = false;
+            infoSink.info.message(EPrefixError, "#version: geometry shaders require non-es profile and version 150 or above");
+            version = 150;
+            if (profile == EEsProfile)
+                profile = ECoreProfile;
+        }
+        break;
+    case EShLangTessControl:
+    case EShLangTessEvaluation:
+        if (version < 400 || profile == EEsProfile) {
+            correct = false;
+            infoSink.info.message(EPrefixError, "#version: tessellation shaders require non-es profile and version 400 or above");
+            version = 400;
+            if (profile == EEsProfile)
+                profile = ECoreProfile;
+        }
+        break;
+    case EShLangCompute:
+        if (version < 430 || profile == EEsProfile) {
+            correct = false;
+            infoSink.info.message(EPrefixError, "#version: compute shaders require non-es profile and version 430 or above");
+            version = 430;
+            if (profile == EEsProfile)
+                profile = ECoreProfile;
+        }
+        break;
+    default:
+        break;
+    }
+
+    if (profile == EEsProfile && version >= 300 && versionNotFirst) {
+        correct = false;
+        infoSink.info.message(EPrefixError, "#version: statement must appear first in es-profile shader; before comments or newlines");
+    }
+
+    return correct;
+}
+
+//
+// Do a partial compile on the given strings for a single compilation unit
+// for a potential deferred link into a single stage (and deferred full compile of that
+// stage through machine-dependent compilation).
+//
+// All preprocessing, parsing, semantic checks, etc. for a single compilation unit
+// are done here.
+//
+// Return:  The tree and other information is filled into the intermediate argument, 
+//          and true is returned by the function for success.
+//
+bool CompileDeferred(
+    TCompiler* compiler,
+    const char* const shaderStrings[],
+    const int numStrings,
+    const int* inputLengths,
+    const EShOptimizationLevel optLevel,
+    const TBuiltInResource* resources,
+    int defaultVersion,         // use 100 for ES environment, 110 for desktop
+    bool forwardCompatible,     // give errors for use of deprecated features
+    EShMessages messages,       // warnings/errors/AST; things to print out
+    TIntermediate& intermediate // returned tree, etc.
+    )
+{
+    if (! InitThread())
+        return false;
+
+    if (numStrings == 0)
+        return true;
+
+    // This must be undone (.pop()) by the caller, after it finishes consuming the created tree.
+    GetThreadPoolAllocator().push();
+    
+    // move to length-based strings, rather than null-terminated strings
+    int* lengths = new int[numStrings];
+    for (int s = 0; s < numStrings; ++s) {
+        if (inputLengths == 0 || inputLengths[s] < 0)
+            lengths[s] = strlen(shaderStrings[s]);
+        else
+            lengths[s] = inputLengths[s];
+    }
+
+    int version;
+    EProfile profile;
+    glslang::TInputScanner input(numStrings, shaderStrings, lengths);
+    bool versionNotFirst = ScanVersion(input, version, profile);
+    bool goodVersion = DeduceVersionProfile(compiler->infoSink, compiler->getLanguage(), versionNotFirst, defaultVersion, version, profile);
+
+    intermediate.setVersion(version);
+    intermediate.setProfile(profile);
+    SetupBuiltinSymbolTable(version, profile);
+    
+    TSymbolTable* cachedTable = SharedSymbolTables[MapVersionToIndex(version)]
+                                                  [profile]
+                                                  [compiler->getLanguage()];
+    
+    // Dynamically allocate the symbol table so we can control when it is deallocated WRT the pool.
+    TSymbolTable* symbolTableMemory = new TSymbolTable;
+    TSymbolTable& symbolTable = *symbolTableMemory;
+    if (cachedTable)
+        symbolTable.adoptLevels(*cachedTable);
+    
+    // Add built-in symbols that are potentially context dependent;
+    // they get popped again further down.
+    AddContextSpecificSymbols(resources, compiler->infoSink, symbolTable, version, profile, compiler->getLanguage());
+
+    TParseContext parseContext(symbolTable, intermediate, false, version, profile, compiler->getLanguage(), compiler->infoSink, forwardCompatible, messages);
+    glslang::TScanContext scanContext(parseContext);
+    TPpContext ppContext(parseContext);
+    parseContext.setScanContext(&scanContext);
+    parseContext.setPpContext(&ppContext);
+    if (! goodVersion)
+        parseContext.addError();
+
+    parseContext.initializeExtensionBehavior();
+
+    //
+    // Parse the application's shaders.  All the following symbol table
+    // work will be throw-away, so push a new allocation scope that can
+    // be thrown away, then push a scope for the current shader's globals.
+    //
+    bool success = true;
+    
+    symbolTable.push();
+    if (! symbolTable.atGlobalLevel())
+        parseContext.infoSink.info.message(EPrefixInternalError, "Wrong symbol table level");
+
+    if (parseContext.insertBuiltInArrayAtGlobalLevel())
+        success = false;
+
+    bool ret = parseContext.parseShaderStrings(ppContext, const_cast<char**>(shaderStrings), lengths, numStrings);
+    if (! ret)
+        success = false;
+    intermediate.addSymbolLinkageNodes(intermediate.getTreeRoot(), parseContext.linkage, parseContext.language, symbolTable);
+
+    // Clean up the symbol table. The AST is self-sufficient now.
+    delete symbolTableMemory;
+
+    if (success && intermediate.getTreeRoot()) {
+        if (optLevel == EShOptNoGeneration)
+            parseContext.infoSink.info.message(EPrefixNone, "No errors.  No code generation or linking was requested.");
+        else
+            success = intermediate.postProcess(intermediate.getTreeRoot(), parseContext.language);
+    } else if (! success) {
+        parseContext.infoSink.info.prefix(EPrefixError);
+        parseContext.infoSink.info << parseContext.getNumErrors() << " compilation errors.  No code generated.\n\n";
+    }
+
+    if (messages & EShMsgAST)
+        intermediate.outputTree(parseContext.infoSink);
+
+    delete [] lengths;
+
+    return success;
 }
 
 } // end anonymous namespace for local functions
 
+
 //
 // ShInitialize() should be called exactly once per process, not per thread.
 //
@@ -322,9 +482,8 @@ int ShInitialize()
     if (! InitProcess())
         return 0;
 
-    if (! PerProcessGPA) { 
+    if (! PerProcessGPA)
         PerProcessGPA = new TPoolAllocator();
-    }
     
     glslang::TScanContext::fillInKeywordMap();
 
@@ -400,8 +559,9 @@ int __fastcall ShFinalize()
 }
 
 //
-// Do an actual compile on the given strings.  The result is left 
-// in the given compile object.
+// Do a full compile on the given strings for a single compilation unit
+// forming a complete stage.  The result of the machine dependent compilation
+// is left in the provided compile object.
 //
 // Return:  The return value is really boolean, indicating
 // success (1) or failure (0).
@@ -419,9 +579,7 @@ int ShCompile(
     EShMessages messages       // warnings/errors/AST; things to print out
     )
 {
-    if (! InitThread())
-        return 0;
-
+    // Map the generic handle to the C++ object
     if (handle == 0)
         return 0;
 
@@ -433,125 +591,26 @@ int ShCompile(
     compiler->infoSink.info.erase();
     compiler->infoSink.debug.erase();
 
-    if (numStrings == 0)
-        return 1;
-
-    GetThreadPoolAllocator().push();
-    
-    // move to length-based strings, rather than null-terminated strings
-    int* lengths = new int[numStrings];
-    for (int s = 0; s < numStrings; ++s) {
-        if (inputLengths == 0 || inputLengths[s] < 0)
-            lengths[s] = strlen(shaderStrings[s]);
-        else
-            lengths[s] = inputLengths[s];
-    }
-
-    int version;
-    EProfile profile;
-    bool versionStatementMissing = false;
-    glslang::TInputScanner input(numStrings, shaderStrings, lengths);
-    bool versionNotFirst = ScanVersion(input, version, profile);
-    if (version == 0) {
-        version = defaultVersion;
-        versionStatementMissing = true;
-    }
-    bool goodProfile = DeduceProfile(compiler->infoSink, version, profile);
-
-    TIntermediate intermediate(version, profile);
-    SetupBuiltinSymbolTable(version, profile);
-    
-    TSymbolTable* cachedTable = SharedSymbolTables[MapVersionToIndex(version)]
-                                                  [profile]
-                                                  [compiler->getLanguage()];
-    
-    // Dynamically allocate the symbol table so we can control when it is deallocated WRT the pool.
-    TSymbolTable* symbolTableMemory = new TSymbolTable;
-    TSymbolTable& symbolTable = *symbolTableMemory;
-    if (cachedTable)
-        symbolTable.adoptLevels(*cachedTable);
-    
-    // Add built-in symbols that are potentially context dependent;
-    // they get popped again further down.
-    AddContextSpecificSymbols(resources, compiler->infoSink, symbolTable, version, profile, compiler->getLanguage());
-
-    TParseContext parseContext(symbolTable, intermediate, false, version, profile, compiler->getLanguage(), compiler->infoSink, forwardCompatible, messages);
-    glslang::TScanContext scanContext(parseContext);
-    TPpContext ppContext(parseContext);
-    parseContext.setScanContext(&scanContext);
-    parseContext.setPpContext(&ppContext);
-
-    TSourceLoc beginning;
-    beginning.line = 1;
-    beginning.string = 0;
-    
-    if (! goodProfile)
-        parseContext.error(beginning, "incorrect", "#version", "");
-    if (versionStatementMissing)
-        parseContext.warn(beginning, "statement missing: use #version on first line of shader", "#version", "");
-    else if (profile == EEsProfile && version >= 300 && versionNotFirst)
-        parseContext.error(beginning, "statement must appear first in ESSL shader; before comments or newlines", "#version", "");
-
-    parseContext.initializeExtensionBehavior();
+    TIntermediate intermediate;
+    bool success = CompileDeferred(compiler, shaderStrings, numStrings, inputLengths, optLevel, resources, defaultVersion, forwardCompatible, messages, intermediate);
 
     //
-    // Parse the application's shaders.  All the following symbol table
-    // work will be throw-away, so push a new allocation scope that can
-    // be thrown away, then push a scope for the current shader's globals.
+    // Call the machine dependent compiler
     //
-    bool success = true;
-    
-    symbolTable.push();
-    if (! symbolTable.atGlobalLevel())
-        parseContext.infoSink.info.message(EPrefixInternalError, "Wrong symbol table level");
-
-    if (parseContext.insertBuiltInArrayAtGlobalLevel())
-        success = false;
-
-    bool ret = parseContext.parseShaderStrings(ppContext, const_cast<char**>(shaderStrings), lengths, numStrings);
-    if (! ret)
-        success = false;
-    intermediate.addSymbolLinkageNodes(intermediate.getTreeRoot(), parseContext.linkage, parseContext.language, symbolTable);
-
-    // Clean up the symbol table before deallocating the pool memory it used.
-    // The AST is self-sufficient now, so it can be done before the rest of compilation/linking.
-    delete symbolTableMemory;
-
-    if (success && intermediate.getTreeRoot()) {
-        if (optLevel == EShOptNoGeneration)
-            parseContext.infoSink.info.message(EPrefixNone, "No errors.  No code generation or linking was requested.");
-        else {
-            success = intermediate.postProcess(intermediate.getTreeRoot(), parseContext.language);
-
-            if (success) {
-                //
-                // Call the machine dependent compiler
-                //
-                if (! compiler->compile(intermediate.getTreeRoot(), parseContext.version, parseContext.profile))
-                    success = false;
-            }
-        }
-    } else if (! success) {
-        parseContext.infoSink.info.prefix(EPrefixError);
-        parseContext.infoSink.info << parseContext.getNumErrors() << " compilation errors.  No code generated.\n\n";
-        success = false;
-    }
-
-    if (messages & EShMsgAST)
-        intermediate.outputTree(parseContext.infoSink);
+    if (success && intermediate.getTreeRoot() && optLevel != EShOptNoGeneration)
+        success = compiler->compile(intermediate.getTreeRoot(), intermediate.getVersion(), intermediate.getProfile());
 
     intermediate.removeTree();
-    //
+
     // Throw away all the temporary memory used by the compilation process.
-    //
+    // The push was done in the CompileDeferred() call above.
     GetThreadPoolAllocator().pop();
-    delete [] lengths;
 
     return success ? 1 : 0;
 }
 
 //
-// Do an actual link on the given compile objects.
+// Link the given compile objects.
 //
 // Return:  The return value of is really boolean, indicating
 // success or failure.
@@ -777,3 +836,135 @@ int ShGetUniformLocation(const ShHandle handle, const char* name)
 
     return uniformMap->getLocation(name);
 }
+
+////////////////////////////////////////////////////////////////////////////////////////////
+//
+// Deferred-Lowering C++ Interface
+// -----------------------------------
+//
+// Below is a new alternate C++ interface that might potentially replace the above
+// opaque handle-based interface.
+//    
+// See more detailed comment in ShaderLang.h
+//
+
+namespace glslang {
+
+class TDeferredCompiler : public TCompiler {
+public:
+    TDeferredCompiler(EShLanguage s, TInfoSink& i) : TCompiler(s, i) { }
+    virtual bool compile(TIntermNode* root, int version = 0, EProfile profile = ENoProfile) { return true; }
+};
+
+
+TShader::TShader(EShLanguage s) 
+    : stage(s)
+{
+    infoSink = new TInfoSink;
+    compiler = new TDeferredCompiler(stage, *infoSink);
+    intermediate = new TIntermediate;
+}
+
+TShader::~TShader()
+{
+    delete infoSink;
+    delete compiler;
+    delete intermediate;
+}
+
+//
+// Turn the shader strings into a parse tree in the TIntermediate.
+//
+bool TShader::parse(const TBuiltInResource* builtInResources, int defaultVersion, bool forwardCompatible, EShMessages messages)
+{
+    return CompileDeferred(compiler, strings, numStrings, 0, EShOptNone, builtInResources, defaultVersion, forwardCompatible, messages, *intermediate);
+
+    // TODO: memory: pool needs to be popped
+}
+
+const char* TShader::getInfoLog()
+{
+    return infoSink->info.c_str();
+}
+
+const char* TShader::getInfoDebugLog()
+{
+    return infoSink->debug.c_str();
+}
+
+TProgram::TProgram()
+{
+    infoSink = new TInfoSink;
+    for (int s = 0; s < EShLangCount; ++s)
+        intermediate[s] = 0;
+}
+
+TProgram::~TProgram()
+{
+    delete infoSink;
+    for (int s = 0; s < EShLangCount; ++s)
+        delete intermediate[s];
+}
+
+//
+// Merge the compilation units within each stage into a single TIntermediate.
+// All starting compilation units need to be the result of calling TShader::parse().
+//
+bool TProgram::link(EShMessages messages)
+{
+    bool error = false;
+
+    for (int s = 0; s < EShLangCount; ++s) {
+        if (! linkStage((EShLanguage)s, messages))
+            error = true;
+    }
+
+    // TODO: Link: cross-stage error checking
+
+    return error;
+}
+
+bool TProgram::linkStage(EShLanguage stage, EShMessages messages)
+{
+    if (stages[stage].size() == 0)
+        return true;
+
+    //
+    // 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;    
+    else {
+        intermediate[stage] = new TIntermediate();
+        merged = intermediate[stage];
+    }
+
+    infoSink->info << "\nLinked " << StageName[stage] << " stage:\n\n";
+
+    if (stages[stage].size() > 1) {
+        std::list<TShader*>::const_iterator it;
+        for (it = stages[stage].begin(); it != stages[stage].end(); ++it)
+            merged->merge(*(*it)->intermediate);
+
+        if (messages & EShMsgAST)
+            merged->outputTree(*infoSink);
+    }
+
+    merged->errorCheck(*infoSink);
+
+    return merged->getNumErrors() > 0;
+}
+
+const char* TProgram::getInfoLog()
+{
+    return infoSink->info.c_str();
+}
+
+const char* TProgram::getInfoDebugLog()
+{
+    return infoSink->debug.c_str();
+}
+
+} // end namespace glslang
index 25e9267..3a4a767 100644 (file)
@@ -51,7 +51,8 @@ const char* StageName[EShLangCount] = {
     "tessellation control",
     "tessellation evaluation",
     "geometry",
-    "fragment"
+    "fragment",
+    "compute"
 };
 
 const char* ProfileName[EProfileCount] = {
index 63fb6d6..e32b4fd 100644 (file)
@@ -56,10 +56,16 @@ class TVariable;
 //
 class TIntermediate {
 public:
-    TIntermediate(int v, EProfile p) : treeRoot(0), profile(p), version(v) { }
+    explicit TIntermediate(int v = 0, EProfile p = ENoProfile) : treeRoot(0), profile(p), version(v), numMains(0), numErrors(0) { }
 
+    void setVersion(int v) { version = v; }
+    int getVersion() const { return version; }
+    void setProfile(EProfile p) { profile = p; }
+    EProfile getProfile() const { return profile; }
     void setTreeRoot(TIntermNode* r) { treeRoot = r; }
     TIntermNode* getTreeRoot() const { return treeRoot; }
+    void addMainCount() { ++numMains; }
+    int getNumErrors() const { return numErrors; }
     
     TIntermSymbol* addSymbol(int Id, const TString&, const TType&, TSourceLoc);
     TIntermTyped* addConversion(TOperator, const TType&, TIntermTyped*);
@@ -93,13 +99,21 @@ public:
     void addSymbolLinkageNode(TIntermAggregate*& linkage, TSymbolTable&, const TString&);
     void addSymbolLinkageNode(TIntermAggregate*& linkage, const TVariable&);
 
+    void merge(TIntermediate&);
+    void errorCheck(TInfoSink& infoSink);
+
     void outputTree(TInfoSink& infoSink);
        void removeTree();
 
 protected:
+    void error(TInfoSink& infoSink, const char*);
+
+protected:
     TIntermNode* treeRoot;
     EProfile profile;
     int version;
+    int numMains;
+    int numErrors;
 
 private:
     void operator=(TIntermediate&); // prevent assignments
index 2e8861b..7b261d4 100644 (file)
@@ -76,26 +76,30 @@ SH_IMPORT_EXPORT int __fastcall ShFinalize();
 // Types of languages the compiler can consume.
 //
 typedef enum {
-       EShLangVertex,
+    EShLangVertex,
     EShLangTessControl,
     EShLangTessEvaluation,
     EShLangGeometry,
-       EShLangFragment,
+    EShLangFragment,
     EShLangCompute,
     EShLangCount,
 } EShLanguage;
 
 typedef enum {
-       EShLangVertexMask         = (1 << EShLangVertex),
+    EShLangVertexMask         = (1 << EShLangVertex),
     EShLangTessControlMask    = (1 << EShLangTessControl),
     EShLangTessEvaluationMask = (1 << EShLangTessEvaluation),
     EShLangGeometryMask       = (1 << EShLangGeometry),
-       EShLangFragmentMask       = (1 << EShLangFragment),
-       EShLangComputeMask        = (1 << EShLangCompute),
+    EShLangFragmentMask       = (1 << EShLangFragment),
+    EShLangComputeMask        = (1 << EShLangCompute),
 } EShLanguageMask;
 
+namespace glslang {
+
 extern const char* StageName[EShLangCount];
 
+} // end namespace glslang
+
 //
 // Types of output the linker will create.
 //
@@ -135,7 +139,7 @@ typedef struct {
 
 typedef struct {
     int numBindings;
-       ShBinding* bindings;  // array of bindings
+    ShBinding* bindings;  // array of bindings
 } ShBindingTable;
 
 //
@@ -220,13 +224,13 @@ SH_IMPORT_EXPORT int ShGetUniformLocation(const ShHandle uniformMap, const char*
 // These are currently unused in the front end, but consumers of the front-end still 
 // be rely on them:
 enum TDebugOptions {
-       EDebugOpNone               = 0x000,
-       EDebugOpIntermediate       = 0x001,
-       EDebugOpAssembly           = 0x002,
+    EDebugOpNone               = 0x000,
+    EDebugOpIntermediate       = 0x001,
+    EDebugOpAssembly           = 0x002,
     EDebugOpObjectCode         = 0x004,
-       EDebugOpLinkMaps           = 0x008,
-       EDebugOpSuppressInfolog    = 0x010,
-       EDebugOpMemoryLeakMode     = 0x020,
+    EDebugOpLinkMaps           = 0x008,
+    EDebugOpSuppressInfolog    = 0x010,
+    EDebugOpMemoryLeakMode     = 0x020,
     EDebugOpTexturePrototypes  = 0x040,
     EDebugOpRelaxedErrors      = 0x080,
     EDebugOpGiveWarnings       = 0x100,
@@ -236,4 +240,76 @@ enum TDebugOptions {
     }  // end extern "C"
 #endif
 
+////////////////////////////////////////////////////////////////////////////////////////////
+//
+// Deferred-Lowering C++ Interface
+// -----------------------------------
+//
+// Below is a new alternate C++ interface that might potentially replace the above
+// opaque handle-based interface.  
+//    
+// The below is further designed to handle multiple compilation units per stage, where
+// the intermediate results, including the parse tree, are preserved until link time,
+// rather than the above interface which is designed to have each compilation unit
+// lowered at compile time.  In above model, linking occurs on the lowered results,
+// whereas in this model intra-stage linking can occur at the parse tree
+// (treeRoot in TIntermediate) level, and then a full stage can be lowered.
+//
+
+#include <list>
+
+class TCompiler;
+class TInfoSink;
+
+namespace glslang {
+
+class TIntermediate;
+class TProgram;
+
+class TShader {
+public:
+    explicit TShader(EShLanguage);
+    virtual ~TShader();
+    void setStrings(char** s, int n) { strings = s; numStrings = n; }
+    bool parse(const TBuiltInResource*, int defaultVersion, bool forwardCompatible, EShMessages);
+    const char* getInfoLog();
+    const char* getInfoDebugLog();
+
+protected:
+    EShLanguage stage;
+    TCompiler* compiler;
+    TIntermediate* intermediate;
+    TInfoSink* infoSink;
+    char** strings;
+    int numStrings;
+
+    friend class TProgram;
+
+private:
+    void operator=(TShader&);
+};
+
+class TProgram {
+public:
+    TProgram();
+    virtual ~TProgram();
+    void addShader(TShader* shader) { stages[shader->stage].push_back(shader); }
+    bool link(EShMessages);
+    const char* getInfoLog();
+    const char* getInfoDebugLog();
+protected:
+    bool linkStage(EShLanguage, EShMessages);
+
+protected:
+    std::list<TShader*> stages[EShLangCount];
+    TIntermediate* intermediate[EShLangCount];
+    TInfoSink* infoSink;
+
+private:
+    void operator=(TProgram&);
+};
+
+} // end namespace glslang
+
+
 #endif // _COMPILER_INTERFACE_INCLUDED_