From c555dddd5344124b4839b09999fca451381d93c9 Mon Sep 17 00:00:00 2001 From: John Kessenich Date: Wed, 17 Jun 2015 02:38:44 +0000 Subject: [PATCH] glslang preprocessing: Add -E option to print out preprocessed GLSL, and do the work needed to generate a preprocessed stream. From Andrew Woloszyn . git-svn-id: https://cvs.khronos.org/svn/repos/ogl/trunk/ecosystem/public/sdk/tools/glslang@31508 e7fa87d3-cd2b-0410-9028-fcbf551c1848 --- StandAlone/StandAlone.cpp | 30 +- .../preprocessor.edge_cases.vert.out | 18 ++ Test/baseResults/preprocessor.errors.vert.out | 18 ++ .../preprocessor.extensions.vert.out | 14 + .../preprocessor.function_macro.vert.out | 23 ++ Test/baseResults/preprocessor.line.vert.out | 26 ++ Test/baseResults/preprocessor.pragma.vert.out | 14 + Test/baseResults/preprocessor.simple.vert.out | 25 ++ Test/preprocessor.edge_cases.vert | 15 + Test/preprocessor.errors.vert | 15 + Test/preprocessor.extensions.vert | 12 + Test/preprocessor.function_macro.vert | 20 ++ Test/preprocessor.line.vert | 39 +++ Test/preprocessor.pragma.vert | 11 + Test/preprocessor.simple.vert | 22 ++ Test/runtests | 10 + Test/test-preprocessor-list | 7 + Test/test-spirv-list | 1 - glslang/MachineIndependent/ParseHelper.cpp | 21 +- glslang/MachineIndependent/ParseHelper.h | 12 +- glslang/MachineIndependent/ShaderLang.cpp | 291 ++++++++++++++++-- .../MachineIndependent/preprocessor/Pp.cpp | 15 +- glslang/Public/ShaderLang.h | 5 + 23 files changed, 609 insertions(+), 55 deletions(-) create mode 100644 Test/baseResults/preprocessor.edge_cases.vert.out create mode 100644 Test/baseResults/preprocessor.errors.vert.out create mode 100644 Test/baseResults/preprocessor.extensions.vert.out create mode 100644 Test/baseResults/preprocessor.function_macro.vert.out create mode 100644 Test/baseResults/preprocessor.line.vert.out create mode 100644 Test/baseResults/preprocessor.pragma.vert.out create mode 100644 Test/baseResults/preprocessor.simple.vert.out create mode 100644 Test/preprocessor.edge_cases.vert create mode 100644 Test/preprocessor.errors.vert create mode 100644 Test/preprocessor.extensions.vert create mode 100644 Test/preprocessor.function_macro.vert create mode 100644 Test/preprocessor.line.vert create mode 100644 Test/preprocessor.pragma.vert create mode 100644 Test/preprocessor.simple.vert create mode 100644 Test/test-preprocessor-list diff --git a/StandAlone/StandAlone.cpp b/StandAlone/StandAlone.cpp index 0c97af54e..906dcbdfb 100644 --- a/StandAlone/StandAlone.cpp +++ b/StandAlone/StandAlone.cpp @@ -71,6 +71,7 @@ enum TOptions { EOptionSpv = 0x0800, EOptionHumanReadableSpv = 0x1000, EOptionDefaultDesktop = 0x2000, + EOptionOutputPreprocessed = 0x4000, }; // @@ -489,6 +490,9 @@ bool ProcessArguments(int argc, char* argv[]) Options |= EOptionSpv; Options |= EOptionLinkProgram; break; + case 'E': + Options |= EOptionOutputPreprocessed; + break; case 'c': Options |= EOptionDumpConfig; break; @@ -536,6 +540,13 @@ bool ProcessArguments(int argc, char* argv[]) } } + // Make sure that -E is not specified alongside -V -H or -l. + if (Options & EOptionOutputPreprocessed && + ((Options & + (EOptionSpv | EOptionHumanReadableSpv | EOptionLinkProgram)))) { + return false; + } + return true; } @@ -608,12 +619,19 @@ void CompileAndLinkShaders() usage(); return; } + const int defaultVersion = Options & EOptionDefaultDesktop? 110: 100; shader->setStrings(shaderStrings, 1); - - if (! shader->parse(&Resources, (Options & EOptionDefaultDesktop) ? 110 : 100, false, messages)) + if (Options & EOptionOutputPreprocessed) { + std::string str; + shader->preprocess(&Resources, defaultVersion, ENoProfile, false, false, messages, &str); + puts(str.c_str()); + FreeFileData(shaderStrings); + continue; + } + if (! shader->parse(&Resources, defaultVersion, false, messages)) CompileFailed = true; - + program.addShader(shader); if (! (Options & EOptionSuppressInfolog)) { @@ -629,7 +647,7 @@ void CompileAndLinkShaders() // Program-level processing... // - if (! program.link(messages)) + if (!(Options & EOptionOutputPreprocessed) && ! program.link(messages)) LinkFailed = true; if (! (Options & EOptionSuppressInfolog)) { @@ -714,7 +732,8 @@ int C_DECL main(int argc, char* argv[]) // 1) linking all arguments together, single-threaded, new C++ interface // 2) independent arguments, can be tackled by multiple asynchronous threads, for testing thread safety, using the old handle interface // - if (Options & EOptionLinkProgram) { + if (Options & EOptionLinkProgram || + Options & EOptionOutputPreprocessed) { glslang::InitializeProcess(); CompileAndLinkShaders(); glslang::FinalizeProcess(); @@ -868,6 +887,7 @@ void usage() "(Each option must be specified separately, but can go anywhere in the command line.)\n" " -V create SPIR-V in file .spv\n" " -H print human readable form of SPIR-V; turns on -V\n" + " -E print pre-processed GLSL; cannot be used with -V, -H, or -l.\n" " -c configuration dump; use to create default configuration file (redirect to a .conf file)\n" " -d default to desktop (#version 110) when there is no version in the shader (default is ES version 100)\n" " -i intermediate tree (glslang AST) is printed out\n" diff --git a/Test/baseResults/preprocessor.edge_cases.vert.out b/Test/baseResults/preprocessor.edge_cases.vert.out new file mode 100644 index 000000000..d667ca7e4 --- /dev/null +++ b/Test/baseResults/preprocessor.edge_cases.vert.out @@ -0,0 +1,18 @@ +#version 310 es + + + + + + + + + + + +void main(){ + gl_Position = vec4(3 + 2 + 2 * 4 + 2 + 3 * 2); +} + + + diff --git a/Test/baseResults/preprocessor.errors.vert.out b/Test/baseResults/preprocessor.errors.vert.out new file mode 100644 index 000000000..409c16aa9 --- /dev/null +++ b/Test/baseResults/preprocessor.errors.vert.out @@ -0,0 +1,18 @@ +#version 310 es + + + + + + + +#error This should show up in pp output . + + + + +int main(){ +} + + + diff --git a/Test/baseResults/preprocessor.extensions.vert.out b/Test/baseResults/preprocessor.extensions.vert.out new file mode 100644 index 000000000..49d180475 --- /dev/null +++ b/Test/baseResults/preprocessor.extensions.vert.out @@ -0,0 +1,14 @@ +#version 310 es + +#extension GL_OES_texture_3D : enable +#extension GL_EXT_frag_depth : disable +#extension GL_EXT_gpu_shader5 : require +#extension GL_EXT_shader_texture_image_samples : warn + +#extension unknown_extension : require + +int main(){ +} + + + diff --git a/Test/baseResults/preprocessor.function_macro.vert.out b/Test/baseResults/preprocessor.function_macro.vert.out new file mode 100644 index 000000000..340d71b0b --- /dev/null +++ b/Test/baseResults/preprocessor.function_macro.vert.out @@ -0,0 +1,23 @@ +#version 310 es + + + + + + + + + + + + + + +int main(){ + gl_Position = vec4(3 + 1, 3 + 4, 3 + 1); + gl_Position = vec4(1 + 2 + 3 + 4 + 5 + 6 + 7 + 8 + 9 + 10 + 11 + 12); + gl_Position = vec4(4 + 3 + 3); +} + + + diff --git a/Test/baseResults/preprocessor.line.vert.out b/Test/baseResults/preprocessor.line.vert.out new file mode 100644 index 000000000..a7742fc67 --- /dev/null +++ b/Test/baseResults/preprocessor.line.vert.out @@ -0,0 +1,26 @@ +#line 300 + +#line 2 + +#line 10 + +#line 2 + +#line 0 + +#line 4 + +#line 8 + +void main(){ + gl_Position = vec4(10); +} +#line 8 4 + +#line 12 3 + +#line 1 + + + + diff --git a/Test/baseResults/preprocessor.pragma.vert.out b/Test/baseResults/preprocessor.pragma.vert.out new file mode 100644 index 000000000..22de1ced2 --- /dev/null +++ b/Test/baseResults/preprocessor.pragma.vert.out @@ -0,0 +1,14 @@ +#version 310 es + +#pragma optimize(on) +#pragma optimize(off) +#pragma debug(on) +#pragma debug(off) + +#pragma undefined_pragma(x,4) + +int main(){ +} + + + diff --git a/Test/baseResults/preprocessor.simple.vert.out b/Test/baseResults/preprocessor.simple.vert.out new file mode 100644 index 000000000..8364576c0 --- /dev/null +++ b/Test/baseResults/preprocessor.simple.vert.out @@ -0,0 +1,25 @@ +#version 310 es + + + + + + + + + + + + + float fn(float x){ return x + 4.0;} + +int main(){ + gl_Position = vec4(1); + gl_Position = clamp(1, 2, 3); + gl_Position = vec4(1); + gl_Position = vec4(1, 2); + gl_Position = vec4(fn(3)); +} + + + diff --git a/Test/preprocessor.edge_cases.vert b/Test/preprocessor.edge_cases.vert new file mode 100644 index 000000000..95bfbb3a1 --- /dev/null +++ b/Test/preprocessor.edge_cases.vert @@ -0,0 +1,15 @@ +#version 310 es +#define X(Y) /* + */ Y + 2 + +#define Y(Z) 2 * Z// asdf + +#define Z(Y) /* + */ \ + 2 /* + */ + 3 \ + * Y + +void main() { + gl_Position = vec4(X(3) + Y(4) + Z(2)); +} diff --git a/Test/preprocessor.errors.vert b/Test/preprocessor.errors.vert new file mode 100644 index 000000000..7de504039 --- /dev/null +++ b/Test/preprocessor.errors.vert @@ -0,0 +1,15 @@ +#version 310 es + +#define X + +#if X + #if Y + #error This should not show up in pp output. + #endif + #error This should show up in pp output. +#else + #error This should not show up in pp output. +#endif + +int main() { +} diff --git a/Test/preprocessor.extensions.vert b/Test/preprocessor.extensions.vert new file mode 100644 index 000000000..6722820eb --- /dev/null +++ b/Test/preprocessor.extensions.vert @@ -0,0 +1,12 @@ +#version 310 es + +#extension GL_OES_texture_3D: enable +#extension GL_EXT_frag_depth: disable +#extension GL_EXT_gpu_shader5: require +#extension GL_EXT_shader_texture_image_samples: warn + +#extension unknown_extension: require + +int main() { +} + diff --git a/Test/preprocessor.function_macro.vert b/Test/preprocessor.function_macro.vert new file mode 100644 index 000000000..577ea7e35 --- /dev/null +++ b/Test/preprocessor.function_macro.vert @@ -0,0 +1,20 @@ +#version 310 es + + +#define X(n) n + 1 +#define Y(n, z) n + z +#define Z(f) X(f) + +#define REALLY_LONG_MACRO_NAME_WITH_MANY_PARAMETERS(X1, X2, X3, X4, X5, X6, X7,\ + X8, X9, X10, X11, X12) X1+X2+X3+X4+X5+X6+X7+X8+X9+X10+X11+X12 + +#define A(\ + Y\ + )\ +4 + 3 + Y + +int main() { + gl_Position = vec4(X(3), Y(3, 4), Z(3)); + gl_Position = vec4(REALLY_LONG_MACRO_NAME_WITH_MANY_PARAMETERS(1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12)); + gl_Position = vec4(A(3)); +} diff --git a/Test/preprocessor.line.vert b/Test/preprocessor.line.vert new file mode 100644 index 000000000..fed6bba2a --- /dev/null +++ b/Test/preprocessor.line.vert @@ -0,0 +1,39 @@ +#line 300 + +#line 2 + + + + + +#line __LINE__ + 3 + + +#line __FILE__ + 2 + +#line __FILE__ * __LINE__ + + +#define X 4 + +#line X + +#undef X + +#define X(y) y + 3 + 2 + +#line X(3) + +void main() { + gl_Position = vec4(__LINE__); +} + +#line X(3) 4 + +#define Z(y, q) \ + y*q*2 q + +#line Z(2, 3) + +#line 1 + diff --git a/Test/preprocessor.pragma.vert b/Test/preprocessor.pragma.vert new file mode 100644 index 000000000..79f5600a8 --- /dev/null +++ b/Test/preprocessor.pragma.vert @@ -0,0 +1,11 @@ +#version 310 es + +#pragma optimize(on) +#pragma optimize(off) +#pragma debug(on) +#pragma debug(off) + +#pragma undefined_pragma(x, 4) + +int main() { +} diff --git a/Test/preprocessor.simple.vert b/Test/preprocessor.simple.vert new file mode 100644 index 000000000..f4749f2a4 --- /dev/null +++ b/Test/preprocessor.simple.vert @@ -0,0 +1,22 @@ +#version 310 es +#define X 1 +#define Y clamp +#define Z X + +#define F 1, 2 + +#define make_function \ + float fn ( float x ) \ + {\ + return x + 4.0; \ + } + +make_function + +int main() { + gl_Position = vec4(X); + gl_Position = Y(1, 2, 3); + gl_Position = vec4(Z); + gl_Position = vec4(F); + gl_Position = vec4(fn(3)); +} diff --git a/Test/runtests b/Test/runtests index be14ee036..09368f140 100755 --- a/Test/runtests +++ b/Test/runtests @@ -54,6 +54,16 @@ while read t; do esac done < test-spirv-list +# +# Preprocessor tests +# +while read t; do + echo Running Preprocessor $t... + b=`basename $t` + $EXE -E $t > $TARGETDIR/$b.out + diff -b $BASEDIR/$b.out $TARGETDIR/$b.out || HASERROR=1 +done < test-preprocessor-list + # # grouped shaders for bulk (faster) tests # diff --git a/Test/test-preprocessor-list b/Test/test-preprocessor-list new file mode 100644 index 000000000..5d665669c --- /dev/null +++ b/Test/test-preprocessor-list @@ -0,0 +1,7 @@ +preprocessor.edge_cases.vert +preprocessor.errors.vert +preprocessor.extensions.vert +preprocessor.function_macro.vert +preprocessor.line.vert +preprocessor.pragma.vert +preprocessor.simple.vert diff --git a/Test/test-spirv-list b/Test/test-spirv-list index 62d0f4447..fed568839 100644 --- a/Test/test-spirv-list +++ b/Test/test-spirv-list @@ -14,7 +14,6 @@ spv.double.comp spv.100ops.frag spv.130.frag spv.140.frag -spv.140.vert spv.150.geom spv.150.vert spv.300BuiltIns.vert diff --git a/glslang/MachineIndependent/ParseHelper.cpp b/glslang/MachineIndependent/ParseHelper.cpp index abb71330f..48b82e6da 100644 --- a/glslang/MachineIndependent/ParseHelper.cpp +++ b/glslang/MachineIndependent/ParseHelper.cpp @@ -5226,14 +5226,6 @@ TIntermNode* TParseContext::addSwitch(TSourceLoc loc, TIntermTyped* expression, return switchNode; } -void TParseContext::setCurrentLine(int line) -{ - currentScanner->setLine(line); - if (lineCallback) { - lineCallback(line); - } -} - void TParseContext::notifyVersion(int line, int version, const char* type_string) { if (versionCallback) { @@ -5241,5 +5233,18 @@ void TParseContext::notifyVersion(int line, int version, const char* type_string } } +void TParseContext::notifyErrorDirective(int line, const char* error_message) +{ + if (errorCallback) { + errorCallback(line, error_message); + } +} + +void TParseContext::notifyLineDirective(int line, bool has_source, int source) +{ + if (lineCallback) { + lineCallback(line, has_source, source); + } +} } // end namespace glslang diff --git a/glslang/MachineIndependent/ParseHelper.h b/glslang/MachineIndependent/ParseHelper.h index 5aaaf289f..ee9d892e5 100644 --- a/glslang/MachineIndependent/ParseHelper.h +++ b/glslang/MachineIndependent/ParseHelper.h @@ -41,6 +41,7 @@ #include "SymbolTable.h" #include "localintermediate.h" #include "Scan.h" +#include #include @@ -200,10 +201,13 @@ public: void addError() { ++numErrors; } int getNumErrors() const { return numErrors; } const TSourceLoc& getCurrentLoc() const { return currentScanner->getSourceLoc(); } - void setCurrentLine(int line); + void setCurrentLine(int line) { currentScanner->setLine(line); } void setCurrentString(int string) { currentScanner->setString(string); } + void setScanner(TInputScanner* scanner) { currentScanner = scanner; } void notifyVersion(int line, int version, const char* type_string); + void notifyErrorDirective(int line, const char* error_message); + void notifyLineDirective(int line, bool has_source, int source); // The following are implemented in Versions.cpp to localize version/profile/stage/extensions control void initializeExtensionBehavior(); @@ -223,8 +227,9 @@ public: void setVersionCallback(const std::function& func) { versionCallback = func; } void setPragmaCallback(const std::function&)>& func) { pragmaCallback = func; } - void setLineCallback(const std::function& func) { lineCallback = func; } + void setLineCallback(const std::function& func) { lineCallback = func; } void setExtensionCallback(const std::function& func) { extensionCallback = func; } + void setErrorCallback(const std::function& func) { errorCallback = func; } protected: void nonInitConstCheck(TSourceLoc, TString& identifier, TType& type); @@ -333,10 +338,11 @@ protected: // These, if set, will be called when a line, pragma ... is preprocessed. // They will be called with any parameters to the original directive. - std::function lineCallback; + std::function lineCallback; std::function&)> pragmaCallback; std::function versionCallback; std::function extensionCallback; + std::function errorCallback; }; } // end namespace glslang diff --git a/glslang/MachineIndependent/ShaderLang.cpp b/glslang/MachineIndependent/ShaderLang.cpp index 59b692cdd..b6a4caa37 100644 --- a/glslang/MachineIndependent/ShaderLang.cpp +++ b/glslang/MachineIndependent/ShaderLang.cpp @@ -41,6 +41,8 @@ // and the shading language compiler/linker. // #include +#include +#include #include "SymbolTable.h" #include "ParseHelper.h" #include "Scan.h" @@ -433,18 +435,17 @@ bool DeduceVersionProfile(TInfoSink& infoSink, EShLanguage stage, bool versionNo return correct; } +// This is the common setup and cleanup code for PreprocessDeferred and +// CompileDeferred. +// It takes any callable with a signature of +// bool (TParseContext& parseContext, TPpContext& ppContext, +// TInputScanner& input, bool versionWillBeError, +// TSymbolTable& , TIntermediate& , +// EShOptimizationLevel , EShMessages ); +// Which returns false if a failure was detected and true otherwise. // -// 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( +template +bool ProcessDeferred( TCompiler* compiler, const char* const shaderStrings[], const int numStrings, @@ -459,7 +460,9 @@ bool CompileDeferred( bool forceDefaultVersionAndProfile, bool forwardCompatible, // give errors for use of deprecated features EShMessages messages, // warnings/errors/AST; things to print out - TIntermediate& intermediate // returned tree, etc. + TIntermediate& intermediate, // returned tree, etc. + ProcessingContext& processingContext, + bool requireNonempty ) { if (! InitThread()) @@ -481,7 +484,7 @@ bool CompileDeferred( // string 2...numStrings+1: user's shader // string numStrings+2: "int;" const int numPre = 2; - const int numPost = 1; + const int numPost = requireNonempty? 1 : 0; size_t* lengths = new size_t[numStrings + numPre + numPost]; const char** strings = new const char*[numStrings + numPre + numPost]; for (int s = 0; s < numStrings; ++s) { @@ -566,7 +569,6 @@ bool CompileDeferred( parseContext.initializeExtensionBehavior(); - bool success = true; // Fill in the strings as outlined above. strings[0] = parseContext.getPreamble(); @@ -574,33 +576,22 @@ bool CompileDeferred( strings[1] = customPreamble; lengths[1] = strlen(strings[1]); assert(2 == numPre); - strings[numStrings + numPre] = "\n int;"; - lengths[numStrings + numPre] = strlen(strings[numStrings + numPre]); + if (requireNonempty) { + strings[numStrings + numPre] = "\n int;"; + lengths[numStrings + numPre] = strlen(strings[numStrings + numPre]); + } TInputScanner fullInput(numStrings + numPre + numPost, strings, lengths, numPre, numPost); // Push a new symbol allocation scope that will get used for the shader's globals. symbolTable.push(); - // Parse the full shader. - if (! parseContext.parseShaderStrings(ppContext, fullInput, versionWillBeError)) - success = false; - intermediate.addSymbolLinkageNodes(parseContext.linkage, parseContext.language, symbolTable); + bool success = processingContext(parseContext, ppContext, fullInput, + versionWillBeError, symbolTable, + intermediate, optLevel, messages); // 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.output(parseContext.infoSink, true); delete [] lengths; delete [] strings; @@ -608,6 +599,216 @@ bool CompileDeferred( return success; } +// DoPreprocessing is a valid ProcessingContext template argument, +// which only performs the preprocessing step of compilation. +// It places the result in the "string" argument to its constructor. +struct DoPreprocessing { + explicit DoPreprocessing(std::string* string): outputString(string) {} + bool operator()(TParseContext& parseContext, TPpContext& ppContext, + TInputScanner& input, bool versionWillBeError, + TSymbolTable& , TIntermediate& , + EShOptimizationLevel , EShMessages ) + { + // This is a list of tokens that do not require a space before or after. + static const std::string unNeededSpaceTokens = ";()[]"; + static const std::string noSpaceBeforeTokens = ","; + glslang::TPpToken token; + + std::stringstream outputStream; + int lastLine = -1; // lastLine is the line number of the last token + // processed. It is tracked in order for new-lines to be inserted when + // a token appears on a new line. + int lastToken = -1; + parseContext.setScanner(&input); + ppContext.setInput(input, versionWillBeError); + + // Inserts newlines and incremnets lastLine until + // lastLine >= line. + auto adjustLine = [&lastLine, &outputStream](int line) { + int tokenLine = line - 1; + while(lastLine < tokenLine) { + if (lastLine >= 0) { + outputStream << std::endl; + } + ++lastLine; + } + }; + + parseContext.setExtensionCallback([&adjustLine, &outputStream]( + int line, const char* extension, const char* behavior) { + adjustLine(line); + outputStream << "#extension " << extension << " : " << behavior; + }); + parseContext.setLineCallback([&lastLine, &outputStream]( + int line, bool hasSource, int sourceNum) { + // SourceNum is the number of the source-string that is being parsed. + if (lastLine != -1) { + outputStream << std::endl; + } + outputStream << "#line " << line; + if (hasSource) { + outputStream << " " << sourceNum; + } + outputStream << std::endl; + lastLine = std::max(line - 1, 1); + }); + + + parseContext.setVersionCallback( + [&adjustLine, &lastLine, &outputStream](int line, int version, const char* str) { + adjustLine(line); + outputStream << "#version " << version; + if (str) { + outputStream << " " << str; + } + outputStream << std::endl; + ++lastLine; + }); + + parseContext.setPragmaCallback([&adjustLine, &outputStream]( + int line, const glslang::TVector& ops) { + adjustLine(line); + outputStream << "#pragma "; + for(size_t i = 0; i < ops.size(); ++i) { + outputStream << ops[i]; + } + }); + + parseContext.setErrorCallback([&adjustLine, &outputStream]( + int line, const char* errorMessage) { + adjustLine(line); + outputStream << "#error " << errorMessage; + }); + while (const char* tok = ppContext.tokenize(&token)) { + int tokenLine = token.loc.line - 1; // start at 0; + bool newLine = false; + while (lastLine < tokenLine) { + if (lastLine > -1) { + outputStream << std::endl; + newLine = true; + } + ++lastLine; + if (lastLine == tokenLine) { + // Don't emit whitespace onto empty lines. + // Copy any whitespace characters at the start of a line + // from the input to the output. + for(int i = 0; i < token.loc.column - 1; ++i) { + outputStream << " "; + } + } + } + + // Output a space in between tokens, but not at the start of a line, + // and also not around special tokens. This helps with readability + // and consistency. + if (!newLine && + lastToken != -1 && + (unNeededSpaceTokens.find((char)token.token) == std::string::npos) && + (unNeededSpaceTokens.find((char)lastToken) == std::string::npos) && + (noSpaceBeforeTokens.find((char)token.token) == std::string::npos)) { + outputStream << " "; + } + lastToken = token.token; + outputStream << tok; + } + outputStream << std::endl; + *outputString = outputStream.str(); + + return true; + } + std::string* outputString; +}; + +// DoFullParse is a valid ProcessingConext template argument for fully +// parsing the shader. It populates the "intermediate" with the AST. +struct DoFullParse{ + bool operator()(TParseContext& parseContext, TPpContext& ppContext, + TInputScanner& fullInput, bool versionWillBeError, + TSymbolTable& symbolTable, TIntermediate& intermediate, + EShOptimizationLevel optLevel, EShMessages messages) + { + bool success = true; + // Parse the full shader. + if (! parseContext.parseShaderStrings(ppContext, fullInput, versionWillBeError)) + success = false; + intermediate.addSymbolLinkageNodes(parseContext.linkage, parseContext.language, symbolTable); + + 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.output(parseContext.infoSink, true); + + return success; + } +}; + +// Take a single compilation unit, and run the preprocessor on it. +// Return: True if there were no issues found in preprocessing, +// False if during preprocessing any unknown version, pragmas or +// extensions were found. +bool PreprocessDeferred( + TCompiler* compiler, + const char* const shaderStrings[], + const int numStrings, + const int* inputLengths, + const char* preamble, + const EShOptimizationLevel optLevel, + const TBuiltInResource* resources, + int defaultVersion, // use 100 for ES environment, 110 for desktop + EProfile defaultProfile, + bool forceDefaultVersionAndProfile, + bool forwardCompatible, // give errors for use of deprecated features + EShMessages messages, // warnings/errors/AST; things to print out + TIntermediate& intermediate, // returned tree, etc. + std::string* outputString) +{ + DoPreprocessing parser(outputString); + return ProcessDeferred(compiler, shaderStrings, numStrings, inputLengths, + preamble, optLevel, resources, defaultVersion, defaultProfile, forceDefaultVersionAndProfile, + forwardCompatible, messages, intermediate, parser, false); +} + + +// +// 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 char* preamble, + const EShOptimizationLevel optLevel, + const TBuiltInResource* resources, + int defaultVersion, // use 100 for ES environment, 110 for desktop + EProfile defaultProfile, + bool forceDefaultVersionAndProfile, + bool forwardCompatible, // give errors for use of deprecated features + EShMessages messages, // warnings/errors/AST; things to print out + TIntermediate& intermediate) // returned tree, etc. +{ + DoFullParse parser; + return ProcessDeferred(compiler, shaderStrings, numStrings, inputLengths, + preamble, optLevel, resources, defaultVersion, defaultProfile, forceDefaultVersionAndProfile, + forwardCompatible, messages, intermediate, parser, true); +} + } // end anonymous namespace for local functions @@ -1026,7 +1227,8 @@ TShader::~TShader() // // Returns true for success. // -bool TShader::parse(const TBuiltInResource* builtInResources, int defaultVersion, EProfile defaultProfile, bool forceDefaultVersionAndProfile, bool forwardCompatible, EShMessages messages) +bool TShader::parse(const TBuiltInResource* builtInResources, int defaultVersion, EProfile defaultProfile, bool forceDefaultVersionAndProfile, + bool forwardCompatible, EShMessages messages) { if (! InitThread()) return false; @@ -1041,7 +1243,28 @@ bool TShader::parse(const TBuiltInResource* builtInResources, int defaultVersion bool TShader::parse(const TBuiltInResource* builtInResources, int defaultVersion, bool forwardCompatible, EShMessages messages) { - return parse(builtInResources, defaultVersion, ENoProfile, false, forwardCompatible, messages); + return parse(builtInResources, defaultVersion, ENoProfile, false, forwardCompatible, messages); +} + +// Fill in a string with the result of preprocessing ShaderStrings +// Returns true if all extensions, pragmas and version strings were valid. +bool TShader::preprocess(const TBuiltInResource* builtInResources, + int defaultVersion, EProfile defaultProfile, bool forceDefaultVersionAndProfile, + bool forwardCompatible, + EShMessages message, std::string* output_string) +{ + if (! InitThread()) + return false; + + pool = new TPoolAllocator(); + SetThreadPoolAllocator(*pool); + if (! preamble) + preamble = ""; + + return PreprocessDeferred(compiler, strings, numStrings, + nullptr, preamble, EShOptNone, builtInResources, + defaultVersion, defaultProfile, forceDefaultVersionAndProfile, forwardCompatible, message, + *intermediate, output_string); } const char* TShader::getInfoLog() diff --git a/glslang/MachineIndependent/preprocessor/Pp.cpp b/glslang/MachineIndependent/preprocessor/Pp.cpp index c309d5541..741f089b0 100644 --- a/glslang/MachineIndependent/preprocessor/Pp.cpp +++ b/glslang/MachineIndependent/preprocessor/Pp.cpp @@ -629,10 +629,15 @@ int TPpContext::CPPline(TPpToken* ppToken) return token; } - int lineRes = 0; + int lineRes = 0; // Line number after macro expansion. + int lineToken = 0; + int fileRes = 0; // Source file number after macro expansion. + bool hasFile = false; bool lineErr = false; + bool fileErr = false; token = eval(token, MIN_PRECEDENCE, false, lineRes, lineErr, ppToken); if (! lineErr) { + lineToken = lineRes; if (token == '\n') ++lineRes; @@ -648,14 +653,15 @@ int TPpContext::CPPline(TPpToken* ppToken) parseContext.setCurrentLine(lineRes); if (token != '\n') { - int fileRes = 0; - bool fileErr = false; token = eval(token, MIN_PRECEDENCE, false, fileRes, fileErr, ppToken); if (! fileErr) parseContext.setCurrentString(fileRes); + hasFile = true; } } - + if (!fileErr && !lineErr) { + parseContext.notifyLineDirective(lineToken, hasFile, fileRes); + } token = extraTokenCheck(lineAtom, ppToken, token); return token; @@ -680,6 +686,7 @@ int TPpContext::CPPerror(TPpToken* ppToken) message.append(" "); token = scanToken(ppToken); } + parseContext.notifyErrorDirective(loc.line, message.c_str()); //store this msg into the shader's information log..set the Compile Error flag!!!! parseContext.error(loc, message.c_str(), "#error", ""); diff --git a/glslang/Public/ShaderLang.h b/glslang/Public/ShaderLang.h index e2e7eb21b..a671221d1 100644 --- a/glslang/Public/ShaderLang.h +++ b/glslang/Public/ShaderLang.h @@ -245,6 +245,7 @@ SH_IMPORT_EXPORT int ShGetUniformLocation(const ShHandle uniformMap, const char* // #include +#include class TCompiler; class TInfoSink; @@ -284,6 +285,10 @@ public: // Equivalent to parse() without a default profile and without forcing defaults. // Provided for backwards compatibility. bool parse(const TBuiltInResource*, int defaultVersion, bool forwardCompatible, EShMessages); + bool preprocess(const TBuiltInResource* builtInResources, + int defaultVersion, EProfile defaultProfile, bool forceDefaultVersionAndProfile, + bool forwardCompatible, + EShMessages message, std::string* outputString); const char* getInfoLog(); const char* getInfoDebugLog();