diff --git a/panda/src/gobj/shader.cxx b/panda/src/gobj/shader.cxx index 85c0e32658..6152b5ebdc 100644 --- a/panda/src/gobj/shader.cxx +++ b/panda/src/gobj/shader.cxx @@ -2385,7 +2385,7 @@ do_read_source(string &into, const Filename &fn, BamCacheRecord *record) { /** * Loads a given GLSL file line by line, and processes any #pragma include and - * once statements. + * once statements, as well as removes any comments. * * The set keeps track of which files we have already included, for checking * recursive includes. @@ -2398,7 +2398,8 @@ r_preprocess_source(ostream &out, const Filename &fn, if (depth > glsl_include_recursion_limit) { shader_cat.error() - << "#pragma include nested too deeply\n"; + << "GLSL includes nested too deeply, raise glsl-include-recursion-limit" + " if necessary\n"; return false; } @@ -2459,56 +2460,226 @@ r_preprocess_source(ostream &out, const Filename &fn, // Iterate over the lines for things we may need to preprocess. string line; + int ext_google_include = 0; // 1 = warn, 2 = enable + int ext_google_line = 0; bool had_include = false; int lineno = 0; while (getline(*source, line)) { ++lineno; - // Check if this line contains a #pragma. - char pragma[64]; - if (line.size() < 8 || - sscanf(line.c_str(), " # pragma %63s", pragma) != 1) { - // Just pass the line through unmodified. - out << line << "\n"; - - // One exception: check for an #endif after an include. We have to - // restore the line number in case the include happened under an #if - // block. - int nread = 0; - if (had_include && sscanf(line.c_str(), " # endif %n", &nread) == 0 && nread >= 6) { - out << "#line " << (lineno + 1) << " " << fileno << "\n"; - } + if (line.empty()) { + out.put('\n'); continue; } - int nread = 0; - if (strcmp(pragma, "include") == 0) { - // Allow both double quotes and angle brackets. - Filename incfn, source_dir; - { - char incfile[2048]; - if (sscanf(line.c_str(), " # pragma%*[ \t]include \"%2047[^\"]\" %n", incfile, &nread) == 1 - && nread == line.size()) { - // A regular include, with double quotes. Probably a local file. - source_dir = full_fn.get_dirname(); - incfn = incfile; + // If the line ends with a backslash, concatenate the following line. + // Preprocessor definitions may be broken up into multiple lines. + while (line[line.size() - 1] == '\\') { + line.resize(line.size() - 1); + string line2; - } else if (sscanf(line.c_str(), " # pragma%*[ \t]include <%2047[^\"]> %n", incfile, &nread) == 1 - && nread == line.size()) { - // Angled includes are also OK, but we don't search in the directory - // of the source file. - incfn = incfile; + if (getline(*source, line2)) { + line += line2; + out.put('\n'); + ++lineno; + } else { + break; + } + } + // Look for comments to strip. This is necessary because comments may + // appear in the middle of or around a preprocessor definition. + size_t line_comment = line.find("//"); + size_t block_comment = line.find("/*"); + if (line_comment < block_comment) { + // A line comment - strip off the rest of the line. + line.resize(line_comment); + + } else if (block_comment < line_comment) { + // A block comment. Search for closing block. + string line2 = line.substr(block_comment + 2); + + // According to the GLSL specification, a block comment is replaced with + // a single whitespace character. + line.resize(block_comment); + line += ' '; + + size_t block_end = line2.find("*/"); + while (block_end == string::npos) { + // Didn't find it - look in the next line. + if (getline(*source, line2)) { + out.put('\n'); + ++lineno; + block_end = line2.find("*/"); } else { - // Couldn't parse it. shader_cat.error() - << "Malformed #pragma include at line " << lineno - << " of file " << fn << ":\n " << line << "\n"; + << "Expected */ before end of file " << fn << "\n"; return false; } } + line += line2.substr(block_end + 2); + } + + // Check if this line contains a #directive. + char directive[64]; + if (line.size() < 8 || sscanf(line.c_str(), " # %63s", directive) != 1) { + // Nope. Just pass the line through unmodified. + out << line << "\n"; + continue; + } + + char pragma[64]; + int nread = 0; + // What kind of directive is it? + if (strcmp(directive, "pragma") == 0 && + sscanf(line.c_str(), " # pragma %63s", pragma) == 1) { + if (strcmp(pragma, "include") == 0) { + // Allow both double quotes and angle brackets. + Filename incfn, source_dir; + { + char incfile[2048]; + if (sscanf(line.c_str(), " # pragma%*[ \t]include \"%2047[^\"]\" %n", incfile, &nread) == 1 + && nread == line.size()) { + // A regular include, with double quotes. Probably a local file. + source_dir = full_fn.get_dirname(); + incfn = incfile; + + } else if (sscanf(line.c_str(), " # pragma%*[ \t]include <%2047[^\"]> %n", incfile, &nread) == 1 + && nread == line.size()) { + // Angled includes are also OK, but we don't search in the directory + // of the source file. + incfn = incfile; + + } else { + // Couldn't parse it. + shader_cat.error() + << "Malformed #pragma include at line " << lineno + << " of file " << fn << ":\n " << line << "\n"; + return false; + } + } + + // OK, great. Process the include. + if (!r_preprocess_source(out, incfn, source_dir, once_files, record, depth + 1)) { + // An error occurred. Pass on the failure. + shader_cat.error(false) << "included at line " + << lineno << " of file " << fn << ":\n " << line << "\n"; + return false; + } + + // Restore the line counter. + out << "#line " << (lineno + 1) << " " << fileno << " // " << fn << "\n"; + had_include = true; + + } else if (strcmp(pragma, "once") == 0) { + // Do a stricter syntax check, just to be extra safe. + if (sscanf(line.c_str(), " # pragma%*[ \t]once %n", &nread) != 0 || + nread != line.size()) { + shader_cat.error() + << "Malformed #pragma once at line " << lineno + << " of file " << fn << ":\n " << line << "\n"; + return false; + } + + once_files.insert(full_fn); + + } else { + // Forward it, the driver will ignore it if it doesn't know it. + out << line << "\n"; + } + + } else if (strcmp(directive, "endif") == 0) { + // Check for an #endif after an include. We have to restore the line + // number in case the include happened under an #if block. + out << line << "\n"; + int nread = 0; + if (had_include) { + out << "#line " << (lineno + 1) << " " << fileno << "\n"; + } + + } else if (strcmp(directive, "extension") == 0) { + // Check for special preprocessing extensions. + char extension[256]; + char behavior[9]; + if (sscanf(line.c_str(), " # extension%*[ \t]%255s : %8s", extension, behavior) == 2) { + // Parse the behavior string. + int mode; + if (strcmp(behavior, "require") == 0 || strcmp(behavior, "enable") == 0) { + mode = 2; + } else if (strcmp(behavior, "warn") == 0) { + mode = 1; + } else if (strcmp(behavior, "disable") == 0) { + mode = 0; + } else { + shader_cat.error() + << "Extension directive specifies invalid behavior at line " + << lineno << " of file " << fn << ":\n " << line << "\n"; + return false; + } + + if (strcmp(extension, "all") == 0) { + if (mode == 2) { + shader_cat.error() + << "Extension directive for 'all' may only specify 'warn' or " + "'disable' at line " << lineno << " of file " << fn + << ":\n " << line << "\n"; + return false; + } + ext_google_include = mode; + ext_google_line = mode; + out << line << "\n"; + + } else if (strcmp(extension, "GL_GOOGLE_include_directive") == 0) { + // Enable the Google extension support for #include statements. + // This also implicitly enables GL_GOOGLE_cpp_style_line_directive. + // This matches the behavior of Khronos' glslang reference compiler. + ext_google_include = mode; + ext_google_line = mode; + + } else if (strcmp(extension, "GL_GOOGLE_cpp_style_line_directive") == 0) { + // Enables strings in #line statements. + ext_google_line = mode; + + } else { + // It's an extension the driver should worry about. + out << line << "\n"; + } + } else { + shader_cat.error() + << "Failed to parse extension directive at line " + << lineno << " of file " << fn << ":\n " << line << "\n"; + return false; + } + } else if (ext_google_include > 0 && strcmp(directive, "include") == 0) { + // Warn about extension use if requested. + if (ext_google_include == 1) { + shader_cat.warning() + << "Extension GL_GOOGLE_include_directive is being used at line " + << lineno << " of file " << fn +#ifndef NDEBUG + << ":\n " << line +#endif + << "\n"; + } + + // This syntax allows only double quotes, not angle brackets. + Filename incfn; + { + char incfile[2048]; + if (sscanf(line.c_str(), " # include%*[ \t]\"%2047[^\"]\" %n", incfile, &nread) != 1 + || nread != line.size()) { + // Couldn't parse it. + shader_cat.error() + << "Malformed #include at line " << lineno + << " of file " << fn << ":\n " << line << "\n"; + return false; + } + incfn = incfile; + } + // OK, great. Process the include. + Filename source_dir = full_fn.get_dirname(); if (!r_preprocess_source(out, incfn, source_dir, once_files, record, depth + 1)) { // An error occurred. Pass on the failure. shader_cat.error(false) << "included at line " @@ -2520,28 +2691,36 @@ r_preprocess_source(ostream &out, const Filename &fn, out << "#line " << (lineno + 1) << " " << fileno << " // " << fn << "\n"; had_include = true; - } else if (strcmp(pragma, "once") == 0) { - // Do a stricter syntax check, just to be extra safe. - if (sscanf(line.c_str(), " # pragma%*[ \t]once %n", &nread) != 0 || - nread != line.size()) { - shader_cat.error() - << "Malformed #pragma once at line " << lineno - << " of file " << fn << ":\n " << line << "\n"; - return false; + } else if (ext_google_line > 0 && strcmp(directive, "line") == 0) { + // It's a #line directive. See if it uses a string instead of number. + char filestr[2048]; + if (sscanf(line.c_str(), " # line%*[ \t]%d%*[ \t]\"%2047[^\"]\" %n", &lineno, filestr, &nread) == 2 + && nread == line.size()) { + // Warn about extension use if requested. + if (ext_google_line == 1) { + shader_cat.warning() + << "Extension GL_GOOGLE_cpp_style_line_directive is being used at line " + << lineno << " of file " << fn +#ifndef NDEBUG + << ":\n " << line +#endif + << "\n"; + } + + // Replace the string line number with an integer. This is something + // we can substitute later when parsing the GLSL log from the driver. + fileno = 2048 + _included_files.size(); + _included_files.push_back(Filename(filestr)); + + out << "#line " << lineno << " " << fileno << " // " << filestr << "\n"; + + } else { + // We couldn't parse the #line directive. Pass it through unmodified. + out << line << "\n"; } - - once_files.insert(full_fn); - - } else if (strcmp(pragma, "optionNV") == 0) { - // This is processed by NVIDIA drivers. Don't touch it. - out << line << "\n"; - } else { - // Forward it, the driver will ignore it if it doesn't know it. + // Different directive (eg. #version). Leave it untouched. out << line << "\n"; - shader_cat.warning() - << "Ignoring unknown pragma directive \"" << pragma << "\" at line " - << lineno << " of file " << fn << ":\n " << line << "\n"; } }