From 69f4b517c2162941466a6ff709fcf9db52881cce Mon Sep 17 00:00:00 2001 From: John Kessenich Date: Wed, 4 Sep 2013 21:19:27 +0000 Subject: [PATCH] Add link validation infrastructure for multiple compilation units per stage. Includes a new, straightforward, C++ interface to the front end. git-svn-id: https://cvs.khronos.org/svn/repos/ogl/trunk/ecosystem/public/sdk/tools/glslang@22927 e7fa87d3-cd2b-0410-9028-fcbf551c1848 --- StandAlone/StandAlone.cpp | 102 +++- Test/baseResults/cppComplexExpr.vert.out | 2 +- Test/baseResults/errors.frag.out | 2 +- Test/baseResults/mains1.frag.out | 33 ++ Test/baseResults/noMain.vert.out | 26 ++ Test/baseResults/pointCoord.frag.out | 2 +- Test/baseResults/versionsClean.frag.out | 2 +- Test/baseResults/versionsErrors.frag.out | 1 - Test/mains.frag | 9 + Test/mains1.frag | 5 + Test/mains2.frag | 5 + Test/noMain.vert | 5 + Test/noMain1.geom | 5 + Test/noMain2.geom | 5 + Test/runtests | 22 +- glslang/Include/ShHandle.h | 2 +- glslang/MachineIndependent/Initialize.cpp | 2 +- glslang/MachineIndependent/Intermediate.cpp | 24 + glslang/MachineIndependent/ParseHelper.cpp | 1 + glslang/MachineIndependent/ParseHelper.h | 1 + glslang/MachineIndependent/ShaderLang.cpp | 441 +++++++++++++----- glslang/MachineIndependent/Versions.cpp | 3 +- .../MachineIndependent/localintermediate.h | 16 +- glslang/Public/ShaderLang.h | 100 +++- 24 files changed, 655 insertions(+), 161 deletions(-) create mode 100644 Test/baseResults/mains1.frag.out create mode 100644 Test/baseResults/noMain.vert.out create mode 100644 Test/mains.frag create mode 100644 Test/mains1.frag create mode 100644 Test/mains2.frag create mode 100644 Test/noMain.vert create mode 100644 Test/noMain1.geom create mode 100644 Test/noMain2.geom diff --git a/StandAlone/StandAlone.cpp b/StandAlone/StandAlone.cpp index 07e52579..3b264886 100644 --- a/StandAlone/StandAlone.cpp +++ b/StandAlone/StandAlone.cpp @@ -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 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 TBuiltInResource* resources) { 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= 0 ? "#### %s %s %d INFO LOG ####\n" : diff --git a/Test/baseResults/cppComplexExpr.vert.out b/Test/baseResults/cppComplexExpr.vert.out index 48ae8951..36fb057c 100644 --- a/Test/baseResults/cppComplexExpr.vert.out +++ b/Test/baseResults/cppComplexExpr.vert.out @@ -1,4 +1,4 @@ -WARNING: 0:1: '#version' : statement missing: use #version on first line of shader +WARNING: #version: statement missing; use #version on first line of shader 0:? Sequence 0:4 Sequence 0:4 move second child to first child (highp float) diff --git a/Test/baseResults/errors.frag.out b/Test/baseResults/errors.frag.out index 73d991b8..279ffd77 100644 --- a/Test/baseResults/errors.frag.out +++ b/Test/baseResults/errors.frag.out @@ -1,4 +1,4 @@ -WARNING: 0:1: '#version' : statement missing: use #version on first line of shader +WARNING: #version: statement missing; use #version on first line of shader ERROR: 0:1: 'main' : function cannot take any parameter(s) ERROR: 0:1: 'int' : main function cannot return a value ERROR: 2 compilation errors. No code generated. diff --git a/Test/baseResults/mains1.frag.out b/Test/baseResults/mains1.frag.out new file mode 100644 index 00000000..69a2078c --- /dev/null +++ b/Test/baseResults/mains1.frag.out @@ -0,0 +1,33 @@ +mains1.frag + +0:3Function Definition: main( (void) +0:3 Function Parameters: + +mains2.frag + +0:3Function Definition: main( (void) +0:3 Function Parameters: + +noMain1.geom +ERROR: #version: geometry shaders require non-es profile and version 150 or above +ERROR: 1 compilation errors. No code generated. + + +0:3Function Definition: foo( (void) +0:3 Function Parameters: + +noMain2.geom + +0:3Function Definition: bar( (void) +0:3 Function Parameters: + + +Linked geometry stage: + +ERROR: Missing entry point: Each stage requires one "void main()" entry point + +Linked fragment stage: + +ERROR: Too many entry points: Each stage can have at most one "void main()" entry point. + + diff --git a/Test/baseResults/noMain.vert.out b/Test/baseResults/noMain.vert.out new file mode 100644 index 00000000..f55806c7 --- /dev/null +++ b/Test/baseResults/noMain.vert.out @@ -0,0 +1,26 @@ +noMain.vert + +0:3Function Definition: foo( (void) +0:3 Function Parameters: + +mains.frag +ERROR: 0:7: 'main' : function already has a body +ERROR: 1 compilation errors. No code generated. + + +ERROR: node is still EOpNull! +0:3 Function Definition: main( (void) +0:3 Function Parameters: +0:7 Function Definition: main( (void) +0:7 Function Parameters: + + +Linked vertex stage: + +ERROR: Missing entry point: Each stage requires one "void main()" entry point + +Linked fragment stage: + +ERROR: Too many entry points: Each stage can have at most one "void main()" entry point. + + diff --git a/Test/baseResults/pointCoord.frag.out b/Test/baseResults/pointCoord.frag.out index 5b01501a..94cef16b 100644 --- a/Test/baseResults/pointCoord.frag.out +++ b/Test/baseResults/pointCoord.frag.out @@ -1,4 +1,4 @@ -WARNING: 0:1: '#version' : statement missing: use #version on first line of shader +WARNING: #version: statement missing; use #version on first line of shader 0:? Sequence 0:5 Function Definition: main( (void) 0:5 Function Parameters: diff --git a/Test/baseResults/versionsClean.frag.out b/Test/baseResults/versionsClean.frag.out index 821da67c..8014ce59 100644 --- a/Test/baseResults/versionsClean.frag.out +++ b/Test/baseResults/versionsClean.frag.out @@ -1,4 +1,4 @@ -ERROR: 0:1: '#version' : statement must appear first in ESSL shader; before comments or newlines +ERROR: #version: statement must appear first in es-profile shader; before comments or newlines ERROR: 1 compilation errors. No code generated. ERROR: node is still EOpNull! diff --git a/Test/baseResults/versionsErrors.frag.out b/Test/baseResults/versionsErrors.frag.out index b13c08ea..41105606 100644 --- a/Test/baseResults/versionsErrors.frag.out +++ b/Test/baseResults/versionsErrors.frag.out @@ -1,5 +1,4 @@ ERROR: #version: versions before 150 do not allow a profile token -ERROR: 0:1: '#version' : incorrect ERROR: 0:38: 'attribute' : not supported in this stage: fragment ERROR: 0:40: 'sampler2DRect' : Reserved word. ERROR: 0:40: 'rectangle texture' : not supported for this version or the enabled extensions diff --git a/Test/mains.frag b/Test/mains.frag new file mode 100644 index 00000000..5756a3e1 --- /dev/null +++ b/Test/mains.frag @@ -0,0 +1,9 @@ +#version 300 es + +void main() +{ +} + +void main() +{ +} diff --git a/Test/mains1.frag b/Test/mains1.frag new file mode 100644 index 00000000..e0de2e1e --- /dev/null +++ b/Test/mains1.frag @@ -0,0 +1,5 @@ +#version 110 + +void main() +{ +} diff --git a/Test/mains2.frag b/Test/mains2.frag new file mode 100644 index 00000000..e0de2e1e --- /dev/null +++ b/Test/mains2.frag @@ -0,0 +1,5 @@ +#version 110 + +void main() +{ +} diff --git a/Test/noMain.vert b/Test/noMain.vert new file mode 100644 index 00000000..e83be047 --- /dev/null +++ b/Test/noMain.vert @@ -0,0 +1,5 @@ +#version 300 es + +void foo() +{ +} diff --git a/Test/noMain1.geom b/Test/noMain1.geom new file mode 100644 index 00000000..86698456 --- /dev/null +++ b/Test/noMain1.geom @@ -0,0 +1,5 @@ +#version 110 + +void foo() +{ +} diff --git a/Test/noMain2.geom b/Test/noMain2.geom new file mode 100644 index 00000000..45c866b6 --- /dev/null +++ b/Test/noMain2.geom @@ -0,0 +1,5 @@ +#version 150 + +void bar() +{ +} diff --git a/Test/runtests b/Test/runtests index dae11f8a..dca678f6 100644 --- a/Test/runtests +++ b/Test/runtests @@ -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 diff --git a/glslang/Include/ShHandle.h b/glslang/Include/ShHandle.h index 659dbecb..5e61a984 100644 --- a/glslang/Include/ShHandle.h +++ b/glslang/Include/ShHandle.h @@ -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 TCompilerList; typedef glslang::TVector THandleList; diff --git a/glslang/MachineIndependent/Initialize.cpp b/glslang/MachineIndependent/Initialize.cpp index 16ccd968..6e65161e 100644 --- a/glslang/MachineIndependent/Initialize.cpp +++ b/glslang/MachineIndependent/Initialize.cpp @@ -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];"); diff --git a/glslang/MachineIndependent/Intermediate.cpp b/glslang/MachineIndependent/Intermediate.cpp index 4d839c3a..9baa2934 100644 --- a/glslang/MachineIndependent/Intermediate.cpp +++ b/glslang/MachineIndependent/Intermediate.cpp @@ -909,6 +909,30 @@ void TIntermediate::addSymbolLinkageNode(TIntermAggregate*& linkage, const TVari linkage = growAggregate(linkage, node); } +// +// 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. // diff --git a/glslang/MachineIndependent/ParseHelper.cpp b/glslang/MachineIndependent/ParseHelper.cpp index e6599e0b..0fe79081 100644 --- a/glslang/MachineIndependent/ParseHelper.cpp +++ b/glslang/MachineIndependent/ParseHelper.cpp @@ -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(); } // diff --git a/glslang/MachineIndependent/ParseHelper.h b/glslang/MachineIndependent/ParseHelper.h index 1d404e9a..f077ce6e 100644 --- a/glslang/MachineIndependent/ParseHelper.h +++ b/glslang/MachineIndependent/ParseHelper.h @@ -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: diff --git a/glslang/MachineIndependent/ShaderLang.cpp b/glslang/MachineIndependent/ShaderLang.cpp index ece846bd..fa2117ac 100644 --- a/glslang/MachineIndependent/ShaderLang.cpp +++ b/glslang/MachineIndependent/ShaderLang.cpp @@ -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(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(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::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 diff --git a/glslang/MachineIndependent/Versions.cpp b/glslang/MachineIndependent/Versions.cpp index 25e92676..3a4a767f 100644 --- a/glslang/MachineIndependent/Versions.cpp +++ b/glslang/MachineIndependent/Versions.cpp @@ -51,7 +51,8 @@ const char* StageName[EShLangCount] = { "tessellation control", "tessellation evaluation", "geometry", - "fragment" + "fragment", + "compute" }; const char* ProfileName[EProfileCount] = { diff --git a/glslang/MachineIndependent/localintermediate.h b/glslang/MachineIndependent/localintermediate.h index 63fb6d6b..e32b4fd2 100644 --- a/glslang/MachineIndependent/localintermediate.h +++ b/glslang/MachineIndependent/localintermediate.h @@ -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 diff --git a/glslang/Public/ShaderLang.h b/glslang/Public/ShaderLang.h index 2e8861b4..7b261d42 100644 --- a/glslang/Public/ShaderLang.h +++ b/glslang/Public/ShaderLang.h @@ -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 + +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 stages[EShLangCount]; + TIntermediate* intermediate[EShLangCount]; + TInfoSink* infoSink; + +private: + void operator=(TProgram&); +}; + +} // end namespace glslang + + #endif // _COMPILER_INTERFACE_INCLUDED_