Browse Source

shaderpipeline: put GLSL preprocessing in ShaderCompiler sub-class

rdb 6 years ago
parent
commit
12f933ab64

+ 99 - 481
panda/src/gobj/shader.cxx

@@ -1585,12 +1585,12 @@ set_compiled(unsigned int format, const char *data, size_t length) {
   _compiled_binary.assign(data, length);
   _compiled_binary.assign(data, length);
 
 
   // Store the compiled shader in the cache.
   // Store the compiled shader in the cache.
-  if (_cache_compiled_shader && !_record.is_null()) {
+  /*if (_cache_compiled_shader && !_record.is_null()) {
     _record->set_data(this);
     _record->set_data(this);
 
 
     BamCache *cache = BamCache::get_global_ptr();
     BamCache *cache = BamCache::get_global_ptr();
     cache->store(_record);
     cache->store(_record);
-  }
+  }*/
 }
 }
 
 
 /**
 /**
@@ -2355,7 +2355,6 @@ Shader(ShaderLanguage lang) :
   _parse(0),
   _parse(0),
   _loaded(false),
   _loaded(false),
   _language(lang),
   _language(lang),
-  _last_modified(0),
   _mat_deps(0),
   _mat_deps(0),
   _cache_compiled_shader(false)
   _cache_compiled_shader(false)
 {
 {
@@ -2427,12 +2426,39 @@ read(const ShaderFile &sfile, BamCacheRecord *record) {
     }
     }
     _filename = sfile;
     _filename = sfile;
 
 
-  } else {
-    if (!do_read_source(Stage::unspecified, sfile._shared, record)) {
+  } else if (_language == SL_Cg || _language == SL_none) {
+    // For historical reasons, we have to open up this file early to determine
+    // some things about it.
+    Filename fn = sfile._shared;
+    VirtualFileSystem *vfs = VirtualFileSystem::get_global_ptr();
+    PT(VirtualFile) vf = vfs->find_file(fn, get_model_path());
+    if (vf == nullptr) {
+      shader_cat.error()
+        << "Could not find shader file: " << fn << "\n";
+      return false;
+    }
+
+    ShaderFile sbody;
+    sbody._separate = false;
+    if (!vf->read_file(sbody._shared, true)) {
+      shader_cat.error()
+        << "Could not read shader file: " << fn << "\n";
       return false;
       return false;
     }
     }
-    _fullpath = _source_files[0];
+
     _filename = sfile;
     _filename = sfile;
+    if (!load(sbody, record)) {
+      return false;
+    }
+
+    for (ShaderModule *module : _modules) {
+      module->set_source_filename(fn);
+    }
+
+  } else {
+    shader_cat.error()
+      << "GLSL shaders must have separate shader bodies!\n";
+    return false;
   }
   }
 
 
   _loaded = true;
   _loaded = true;
@@ -2481,10 +2507,29 @@ load(const ShaderFile &sbody, BamCacheRecord *record) {
       return false;
       return false;
     }
     }
 
 
-  } else {
-    if (!do_load_source(Stage::unspecified, sbody._shared, record)) {
+  } else if (_language == SL_Cg || _language == SL_none) {
+    if (_language == SL_none && !has_cg_header(sbody._shared)) {
+      shader_cat.error()
+        << "Unable to determine shader language of created-shader\n";
+      return false;
+    }
+    _language = SL_Cg;
+
+    if (!do_load_source(Stage::vertex, sbody._shared, record)) {
+      return false;
+    }
+    if (!do_load_source(Stage::fragment, sbody._shared, record)) {
+      return false;
+    }
+    if (sbody._shared.find("gshader") != string::npos &&
+        !do_load_source(Stage::geometry, sbody._shared, record)) {
       return false;
       return false;
     }
     }
+
+  } else {
+    shader_cat.error()
+      << "GLSL shaders must have separate shader bodies!\n";
+    return false;
   }
   }
 
 
   _loaded = true;
   _loaded = true;
@@ -2499,7 +2544,6 @@ load(const ShaderFile &sbody, BamCacheRecord *record) {
  */
  */
 bool Shader::
 bool Shader::
 do_read_source(Stage stage, const Filename &fn, BamCacheRecord *record) {
 do_read_source(Stage stage, const Filename &fn, BamCacheRecord *record) {
-  std::string into;
   VirtualFileSystem *vfs = VirtualFileSystem::get_global_ptr();
   VirtualFileSystem *vfs = VirtualFileSystem::get_global_ptr();
   PT(VirtualFile) vf = vfs->find_file(fn, get_model_path());
   PT(VirtualFile) vf = vfs->find_file(fn, get_model_path());
   if (vf == nullptr) {
   if (vf == nullptr) {
@@ -2508,27 +2552,38 @@ do_read_source(Stage stage, const Filename &fn, BamCacheRecord *record) {
     return false;
     return false;
   }
   }
 
 
-  shader_cat.info() << "Reading shader file: " << fn << "\n";
-
-  if (!vf->read_file(into, true)) {
+  std::istream *in = vf->open_read_file(true);
+  if (in == nullptr) {
     shader_cat.error()
     shader_cat.error()
-      << "Could not read shader file: " << fn << "\n";
+      << "Could not open shader file for reading: " << fn << "\n";
     return false;
     return false;
   }
   }
 
 
-  do_load_source(stage, into, record);
+  PT(BamCacheRecord) record_pt;
+  if (record == nullptr) {
+    BamCache *cache = BamCache::get_global_ptr();
+    record_pt = cache->lookup(vf->get_filename(), "smo");
+    record = record_pt.p();
+  }
 
 
   if (record != nullptr) {
   if (record != nullptr) {
     record->add_dependent_file(vf);
     record->add_dependent_file(vf);
   }
   }
 
 
-  _last_modified = std::max(_last_modified, vf->get_timestamp());
-  _source_files.push_back(vf->get_filename());
+  shader_cat.info() << "Reading shader file: " << fn << "\n";
+  if (!do_read_source(stage, *in, record)) {
+    vf->close_read_file(in);
+    return false;
+  }
+
+  //_last_modified = std::max(_last_modified, vf->get_timestamp());
+  //_source_files.push_back(vf->get_filename());
 
 
   // Update module source filename, should find a better way to do this...
   // Update module source filename, should find a better way to do this...
   PT(ShaderModule) module = _modules.back();
   PT(ShaderModule) module = _modules.back();
   module->set_source_filename(fn);
   module->set_source_filename(fn);
 
 
+  vf->close_read_file(in);
   return true;
   return true;
 }
 }
 
 
@@ -2540,55 +2595,20 @@ do_read_source(Stage stage, const Filename &fn, BamCacheRecord *record) {
  * it 'invalid'.
  * it 'invalid'.
  */
  */
 bool Shader::
 bool Shader::
-do_load_source(ShaderModule::Stage stage, const std::string &source, BamCacheRecord *record) {
-  PT(ShaderModuleGlsl) module = new ShaderModuleGlsl(stage);
-  std::string &into = module->_raw_source;
+do_read_source(ShaderModule::Stage stage, std::istream &in, BamCacheRecord *record) {
+  ShaderCompiler *compiler = get_compiler(_language);
+  nassertr(compiler != nullptr, false);
 
 
-  if (stage == Stage::unspecified) {
-    // Determine which language the shader is written in.
-    if (_language == SL_none) {
-      if (has_cg_header(source)) {
-        _language = SL_Cg;
-      } else {
-        shader_cat.error()
-          << "Unable to determine shader language of " << source << "\n";
-        return false;
-      }
-    } else if (_language == SL_GLSL) {
-      shader_cat.error()
-        << "GLSL shaders must have separate shader bodies!\n";
-      return false;
-    }
-
-    if (_language != SL_Cg) {
-      shader_cat.error()
-        << "Shader is not in a supported shader-language.\n";
-      return false;
-    }
-
-  }
-
-  if (_language == SL_GLSL && glsl_preprocess) {
-    // Preprocess the GLSL file as we read it.
-    std::set<Filename> open_files;
-    std::ostringstream sstr;
-    std::istringstream in(source);
-    if (!r_preprocess_source(module, sstr, in, Filename("created-shader"), Filename(),
-                             open_files, record)) {
-      return false;
-    }
-    into = sstr.str();
-
-  } else {
-    into = source;
+  PT(ShaderModule) module = compiler->compile_now(stage, in, "created-shader", record);
+  if (!module) {
+    return false;
   }
   }
 
 
   if (_language == SL_Cg) {
   if (_language == SL_Cg) {
 #ifdef HAVE_CG
 #ifdef HAVE_CG
-    ShaderCompilerCg *cg_compiler = DCAST(ShaderCompilerCg, get_compiler(SL_Cg));
-    cg_compiler->get_profile_from_header(into, _default_caps);
+    ShaderCompilerCg *cg_compiler = DCAST(ShaderCompilerCg, compiler);
+    cg_compiler->get_profile_from_header(_text._shared, _default_caps);
 
 
-    _text._shared = into;
     if (!cg_analyze_shader(_default_caps)) {
     if (!cg_analyze_shader(_default_caps)) {
       shader_cat.error()
       shader_cat.error()
         << "Shader encountered an error.\n";
         << "Shader encountered an error.\n";
@@ -2600,420 +2620,23 @@ do_load_source(ShaderModule::Stage stage, const std::string &source, BamCacheRec
 #endif
 #endif
   }
   }
 
 
-  // Strip trailing whitespace.
-  while (!into.empty() && isspace(into[into.size() - 1])) {
-    into.resize(into.size() - 1);
-  }
-
-  // Except add back a newline at the end, which is needed by Intel drivers.
-  into += "\n";
-
   _modules.push_back(std::move(module));
   _modules.push_back(std::move(module));
+  _module_mask |= (1 << (int)stage);
 
 
   return true;
   return true;
 }
 }
 
 
 /**
 /**
- * Loads a given GLSL file line by line, and processes any #pragma include and
- * once statements, as well as removes any comments.
- *
- * The set keeps track of which files we have already included, for checking
- * recursive includes.
- */
-bool Shader::
-r_preprocess_include(ShaderModuleGlsl *module,
-                     ostream &out, const Filename &fn,
-                     const Filename &source_dir,
-                     std::set<Filename> &once_files,
-                     BamCacheRecord *record, int depth) {
-
-  if (depth > glsl_include_recursion_limit) {
-    shader_cat.error()
-      << "GLSL includes nested too deeply, raise glsl-include-recursion-limit"
-         " if necessary\n";
-    return false;
-  }
-
-  DSearchPath path(get_model_path());
-  if (!source_dir.empty()) {
-    path.prepend_directory(source_dir);
-  }
-
-  VirtualFileSystem *vfs = VirtualFileSystem::get_global_ptr();
-  PT(VirtualFile) vf = vfs->find_file(fn, path);
-  if (vf == nullptr) {
-    shader_cat.error()
-      << "Could not find shader include: " << fn << "\n";
-    return false;
-  }
-
-  Filename full_fn = vf->get_filename();
-  if (once_files.find(full_fn) != once_files.end()) {
-    // If this file had a #pragma once, just move on.
-    return true;
-  }
-
-  istream *source = vf->open_read_file(true);
-  if (source == nullptr) {
-    shader_cat.error()
-      << "Could not open shader include: " << fn << "\n";
-    return false;
-  }
-
-  if (record != nullptr) {
-    record->add_dependent_file(vf);
-  }
-  _last_modified = std::max(_last_modified, vf->get_timestamp());
-  _source_files.push_back(full_fn);
-
-  // We give each file an unique index.  This is so that we can identify a
-  // particular shader in the error output.  We offset them by 2048 so that
-  // they are more recognizable.  GLSL doesn't give us anything more useful
-  // than that, unfortunately.  Don't do this for the top-level file, though.
-  // We don't want anything to get in before a potential #version directive.
-  int fileno = 0;
-  fileno = 2048 + module->_included_files.size();
-
-  // Write it into the vector so that we can substitute it later when we are
-  // parsing the GLSL error log.  Don't store the full filename because it
-  // would just be too long to display.
-  module->_included_files.push_back(fn);
-
-  if (shader_cat.is_debug()) {
-    shader_cat.debug()
-      << "Preprocessing shader include " << fileno << ": " << fn << "\n";
-  }
-
-  bool result = r_preprocess_source(module, out, *source, fn, full_fn, once_files, record, fileno, depth);
-  vf->close_read_file(source);
-  return result;
-}
-
-/**
- * Loads a given GLSL stream line by line, processing any #pragma include and
- * once statements, as well as removing any comments.
+ * Loads the shader file from the given string into the given string,
+ * performing any pre-processing on it that may be necessary.
  *
  *
- * The set keeps track of which files we have already included, for checking
- * recursive includes.
+ * Returns false if there was an error with this shader bad enough to consider
+ * it 'invalid'.
  */
  */
 bool Shader::
 bool Shader::
-r_preprocess_source(ShaderModuleGlsl *module,
-                    ostream &out, istream &in, const Filename &fn,
-                    const Filename &full_fn, std::set<Filename> &once_files,
-                    BamCacheRecord *record, int fileno, int depth) {
-
-  // 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;
-  bool had_version = false;
-  int lineno = 0;
-  bool write_line_directive = (fileno != 0);
-
-  while (std::getline(in, line)) {
-    ++lineno;
-
-    if (line.empty()) {
-      // We still write a newline to make sure the line numbering remains
-      // consistent, unless we are about to write a #line directive anyway.
-      if (!write_line_directive) {
-        out.put('\n');
-      }
-      continue;
-    }
-
-    // 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;
-
-      if (std::getline(in, line2)) {
-        line += line2;
-        if (!write_line_directive) {
-          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 (std::getline(in, line2)) {
-          if (!write_line_directive) {
-            out.put('\n');
-          }
-          ++lineno;
-          block_end = line2.find("*/");
-        } else {
-          shader_cat.error()
-            << "Expected */ before end of file " << fn << "\n";
-          return false;
-        }
-      }
-
-      line += line2.substr(block_end + 2);
-    }
-
-    // Strip trailing whitespace.
-    while (!line.empty() && isspace(line[line.size() - 1])) {
-      line.resize(line.size() - 1);
-    }
-
-    if (line.empty()) {
-      if (!write_line_directive) {
-        out.put('\n');
-      }
-      continue;
-    }
-
-    // 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.
-      if (write_line_directive) {
-        out << "#line " << lineno << " " << fileno << " // " << fn << "\n";
-        write_line_directive = false;
-      }
-      out << line << "\n";
-      continue;
-    }
-
-    char pragma[64];
-    size_t 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[^\"]\" %zn", 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[^\"]> %zn", 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_include(module, 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.
-        write_line_directive = true;
-        had_include = true;
-        continue;
-
-      } else if (strcmp(pragma, "once") == 0) {
-        // Do a stricter syntax check, just to be extra safe.
-        if (sscanf(line.c_str(), " # pragma%*[ \t]once %zn", &nread) != 0 ||
-            nread != line.size()) {
-          shader_cat.error()
-            << "Malformed #pragma once at line " << lineno
-            << " of file " << fn << ":\n  " << line << "\n";
-          return false;
-        }
-
-        if (fileno == 0) {
-          shader_cat.warning()
-            << "#pragma once in main file at line "
-            << lineno << " of file " << fn
-#ifndef NDEBUG
-            << ":\n  " << line
-#endif
-            << "\n";
-        }
-
-        if (!full_fn.empty()) {
-          once_files.insert(full_fn);
-        }
-        continue;
-      }
-      // Otherwise, just pass it through to the driver.
-
-    } 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.
-      if (had_include) {
-        write_line_directive = true;
-      }
-
-    } else if (strcmp(directive, "version") == 0) {
-      had_version = true;
-
-    } else if (strcmp(directive, "extension") == 0) {
-      // Check for special preprocessing extensions.
-      char extension[256];
-      char behavior[9];
-      if (sscanf(line.c_str(), " # extension%*[ \t]%255[^: \t] : %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;
-          // Still pass it through to the driver, so it can enable other
-          // extensions.
-
-        } 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;
-          continue;
-
-        } else if (strcmp(extension, "GL_GOOGLE_cpp_style_line_directive") == 0) {
-          // Enables strings in #line statements.
-          ext_google_line = mode;
-          continue;
-        }
-      } 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[^\"]\" %zn", 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_include(module, 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.
-      write_line_directive = true;
-      had_include = true;
-      continue;
-
-    } 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[^\"]\" %zn", &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 + module->_included_files.size();
-        module->_included_files.push_back(Filename(filestr));
-
-        out << "#line " << lineno << " " << fileno << " // " << filestr << "\n";
-        continue;
-      }
-    }
-
-    if (write_line_directive) {
-      out << "#line " << lineno << " " << fileno << " // " << fn << "\n";
-      write_line_directive = false;
-    }
-    out << line << "\n";
-  }
-
-  if (fileno == 0 && !had_version) {
-    shader_cat.warning()
-      << "GLSL shader " << fn << " does not contain a #version line!\n";
-  }
-
-  return true;
+do_load_source(ShaderModule::Stage stage, const std::string &source, BamCacheRecord *record) {
+  std::istringstream in(source);
+  return do_read_source(stage, in, record);
 }
 }
 
 
 /**
 /**
@@ -3022,18 +2645,11 @@ r_preprocess_source(ShaderModuleGlsl *module,
  */
  */
 bool Shader::
 bool Shader::
 check_modified() const {
 check_modified() const {
-  VirtualFileSystem *vfs = VirtualFileSystem::get_global_ptr();
-
-  pvector<Filename>::const_iterator it;
-  for (it = _source_files.begin(); it != _source_files.end(); ++it) {
-    const Filename &fn = (*it);
-
-    PT(VirtualFile) vfile = vfs->get_file(fn, true);
-    if (vfile == nullptr || vfile->get_timestamp() > _last_modified) {
+  for (ShaderModule *module : _modules) {
+    if (module->_record != nullptr && !module->_record->dependents_unchanged()) {
       return true;
       return true;
     }
     }
   }
   }
-
   return false;
   return false;
 }
 }
 
 
@@ -3176,18 +2792,20 @@ load_compute(ShaderLanguage lang, const Filename &fn) {
   PT(BamCacheRecord) record = cache->lookup(fullpath, "sho");
   PT(BamCacheRecord) record = cache->lookup(fullpath, "sho");
   if (record != nullptr) {
   if (record != nullptr) {
     if (record->has_data()) {
     if (record->has_data()) {
-      shader_cat.info()
-        << "Compute shader " << fn << " was found in disk cache.\n";
-
-      return DCAST(Shader, record->get_data());
+      PT(Shader) shader = DCAST(Shader, record->get_data());
+      if (shader->_module_mask == (1 << (int)Stage::compute)) {
+        shader_cat.info()
+          << "Compute shader " << fn << " was found in disk cache.\n";
+        return shader;
+      }
     }
     }
   }
   }
 
 
   PT(Shader) shader = new Shader(lang);
   PT(Shader) shader = new Shader(lang);
-
-  if (!shader->read(sfile, record)) {
+  if (!shader->do_read_source(Stage::compute, fullpath, record)) {
     return nullptr;
     return nullptr;
   }
   }
+
   _load_table[sfile] = shader;
   _load_table[sfile] = shader;
 
 
   /*if (cache_generated_shaders) {
   /*if (cache_generated_shaders) {
@@ -3200,9 +2818,9 @@ load_compute(ShaderLanguage lang, const Filename &fn) {
 
 
   // It makes little sense to cache the shader before compilation, so we keep
   // It makes little sense to cache the shader before compilation, so we keep
   // the record for when we have the compiled the shader.
   // the record for when we have the compiled the shader.
-  std::swap(shader->_record, record);
+  //shader->_record = std::move(record);
   shader->_cache_compiled_shader = BamCache::get_global_ptr()->get_cache_compiled_shaders();
   shader->_cache_compiled_shader = BamCache::get_global_ptr()->get_cache_compiled_shaders();
-  shader->_fullpath = shader->_source_files[0];
+  shader->_fullpath = std::move(fullpath);
   return shader;
   return shader;
 }
 }
 
 

+ 3 - 18
panda/src/gobj/shader.h

@@ -33,7 +33,6 @@
 #include "pta_LVecBase2.h"
 #include "pta_LVecBase2.h"
 #include "epvector.h"
 #include "epvector.h"
 #include "asyncFuture.h"
 #include "asyncFuture.h"
-#include "bamCacheRecord.h"
 #include "shaderModule.h"
 #include "shaderModule.h"
 
 
 #ifdef HAVE_CG
 #ifdef HAVE_CG
@@ -44,6 +43,7 @@ typedef struct _CGprogram   *CGprogram;
 typedef struct _CGparameter *CGparameter;
 typedef struct _CGparameter *CGparameter;
 #endif
 #endif
 
 
+class BamCacheRecord;
 class ShaderModuleGlsl;
 class ShaderModuleGlsl;
 class ShaderCompiler;
 class ShaderCompiler;
 
 
@@ -570,6 +570,7 @@ public:
 
 
   typedef pvector<PT(ShaderModule)> Modules;
   typedef pvector<PT(ShaderModule)> Modules;
   Modules _modules;
   Modules _modules;
+  int _module_mask = 0;
 
 
 protected:
 protected:
   ShaderFile _filename;
   ShaderFile _filename;
@@ -580,12 +581,6 @@ protected:
 
 
   typedef pvector<Filename> Filenames;
   typedef pvector<Filename> Filenames;
 
 
-  // Stores full paths, and includes the fullpaths of the shaders themselves
-  // as well as the includes.
-  Filenames _source_files;
-  time_t _last_modified;
-
-  PT(BamCacheRecord) _record;
   bool _cache_compiled_shader;
   bool _cache_compiled_shader;
   unsigned int _compiled_format;
   unsigned int _compiled_format;
   std::string _compiled_binary;
   std::string _compiled_binary;
@@ -612,18 +607,8 @@ private:
   bool read(const ShaderFile &sfile, BamCacheRecord *record = nullptr);
   bool read(const ShaderFile &sfile, BamCacheRecord *record = nullptr);
   bool load(const ShaderFile &sbody, BamCacheRecord *record = nullptr);
   bool load(const ShaderFile &sbody, BamCacheRecord *record = nullptr);
   bool do_read_source(ShaderModule::Stage stage, const Filename &fn, BamCacheRecord *record);
   bool do_read_source(ShaderModule::Stage stage, const Filename &fn, BamCacheRecord *record);
+  bool do_read_source(ShaderModule::Stage stage, std::istream &in, BamCacheRecord *record);
   bool do_load_source(ShaderModule::Stage stage, const std::string &source, BamCacheRecord *record);
   bool do_load_source(ShaderModule::Stage stage, const std::string &source, BamCacheRecord *record);
-  bool r_preprocess_include(ShaderModuleGlsl *module,
-                            std::ostream &out, const Filename &fn,
-                            const Filename &source_dir,
-                            std::set<Filename> &open_files,
-                            BamCacheRecord *record, int depth);
-  bool r_preprocess_source(ShaderModuleGlsl *module,
-                           std::ostream &out, std::istream &in,
-                           const Filename &fn, const Filename &full_fn,
-                           std::set<Filename> &open_files,
-                           BamCacheRecord *record,
-                           int fileno = 0, int depth = 0);
 
 
   bool check_modified() const;
   bool check_modified() const;
   ShaderCompiler *get_compiler(ShaderLanguage lang) const;
   ShaderCompiler *get_compiler(ShaderLanguage lang) const;

+ 30 - 1
panda/src/shaderpipeline/shaderCompiler.cxx

@@ -12,7 +12,9 @@
  */
  */
 
 
 #include "shaderCompiler.h"
 #include "shaderCompiler.h"
-
+#include "virtualFileSystem.h"
+#include "config_putil.h"
+#include "bamCacheRecord.h"
 
 
 TypeHandle ShaderCompiler::_type_handle;
 TypeHandle ShaderCompiler::_type_handle;
 
 
@@ -29,3 +31,30 @@ ShaderCompiler() {
 ShaderCompiler::
 ShaderCompiler::
 ~ShaderCompiler() {
 ~ShaderCompiler() {
 }
 }
+
+/**
+ * Loads and compiles the code from the given shader file, producing a
+ * ShaderModule on success.
+ */
+PT(ShaderModule) ShaderCompiler::
+compile_now(ShaderModule::Stage stage, const Filename &fn, BamCacheRecord *record) const {
+  VirtualFileSystem *vfs = VirtualFileSystem::get_global_ptr();
+  PT(VirtualFile) vf = vfs->find_file(fn, get_model_path());
+  if (vf == nullptr) {
+    shader_cat.error()
+      << "Could not find shader file: " << fn << "\n";
+    return nullptr;
+  }
+
+  std::istream *in = vf->open_read_file(true);
+  if (vf == nullptr) {
+    shader_cat.error()
+      << "Could not open shader file for reading: " << fn << "\n";
+    return nullptr;
+  }
+
+  // The default implementation calls the version that takes an istream.
+  PT(ShaderModule) module = compile_now(stage, *in, fn, record);
+  vf->close_read_file(in);
+  return module;
+}

+ 5 - 1
panda/src/shaderpipeline/shaderCompiler.h

@@ -38,7 +38,11 @@ public:
 PUBLISHED:
 PUBLISHED:
   virtual std::string get_name() const=0;
   virtual std::string get_name() const=0;
   virtual ShaderLanguages get_languages() const=0;
   virtual ShaderLanguages get_languages() const=0;
-  virtual PT(ShaderModule) compile_now(Stage stage, std::istream &in) const=0;
+  virtual PT(ShaderModule) compile_now(Stage stage, const Filename &path,
+                                       BamCacheRecord *record = nullptr) const;
+  virtual PT(ShaderModule) compile_now(Stage stage, std::istream &in,
+                                       const std::string &filename = "created-shader",
+                                       BamCacheRecord *record = nullptr) const=0;
 
 
 public:
 public:
   static TypeHandle get_class_type() {
   static TypeHandle get_class_type() {

+ 6 - 1
panda/src/shaderpipeline/shaderCompilerCg.cxx

@@ -129,7 +129,12 @@ get_languages() const {
   };
   };
 }
 }
 
 
+/**
+ * Compiles the source code from the given input stream, producing a
+ * ShaderModule on success.
+ */
 PT(ShaderModule) ShaderCompilerCg::
 PT(ShaderModule) ShaderCompilerCg::
-compile_now(ShaderModule::Stage stage, std::istream &in) const {
+compile_now(ShaderModule::Stage stage, std::istream &in,
+            const std::string &filename, BamCacheRecord *record) const {
   return nullptr;
   return nullptr;
 }
 }

+ 3 - 2
panda/src/shaderpipeline/shaderCompilerCg.h

@@ -31,8 +31,9 @@ public:
 PUBLISHED:
 PUBLISHED:
   virtual std::string get_name() const override;
   virtual std::string get_name() const override;
   virtual ShaderLanguages get_languages() const override;
   virtual ShaderLanguages get_languages() const override;
-  virtual PT(ShaderModule) compile_now(Stage stage, std::istream &in) const override;
-
+  virtual PT(ShaderModule) compile_now(Stage stage, std::istream &in,
+                                       const std::string &filename = "created-shader",
+                                       BamCacheRecord *record = nullptr) const override;
 
 
 public:
 public:
   static TypeHandle get_class_type() {
   static TypeHandle get_class_type() {

+ 427 - 2
panda/src/shaderpipeline/shaderCompilerGlslPreProc.cxx

@@ -44,7 +44,432 @@ get_languages() const {
   };
   };
 }
 }
 
 
+/**
+ * Compiles the source code from the given input stream, producing a
+ * ShaderModule on success.
+ */
 PT(ShaderModule) ShaderCompilerGlslPreProc::
 PT(ShaderModule) ShaderCompilerGlslPreProc::
-compile_now(ShaderModule::Stage stage, std::istream &in) const {
-  return new ShaderModuleGlsl(stage);
+compile_now(ShaderModule::Stage stage, std::istream &in,
+            const std::string &filename, BamCacheRecord *record) const {
+  PT(ShaderModuleGlsl) module = new ShaderModuleGlsl(stage);
+  std::string &into = module->_raw_source;
+
+  std::ostringstream sstr;
+  std::set<Filename> open_files;
+  if (r_preprocess_source(module, sstr, in, filename, Filename(), open_files, record)) {
+    into = sstr.str();
+
+    // Strip trailing whitespace.
+    while (!into.empty() && isspace(into[into.size() - 1])) {
+      into.resize(into.size() - 1);
+    }
+
+    // Except add back a newline at the end, which is needed by Intel drivers.
+    into += "\n";
+
+    module->_record = record;
+    return module;
+  } else {
+    return nullptr;
+  }
+}
+
+/**
+ * Loads a given GLSL stream line by line, processing any #pragma include and
+ * once statements, as well as removing any comments.
+ *
+ * The set keeps track of which files we have already included, for checking
+ * recursive includes.
+ */
+bool ShaderCompilerGlslPreProc::
+r_preprocess_source(ShaderModuleGlsl *module,
+                    std::ostream &out, std::istream &in, const std::string &fn,
+                    const Filename &full_fn, std::set<Filename> &once_files,
+                    BamCacheRecord *record, int fileno, int depth) const {
+
+  // Iterate over the lines for things we may need to preprocess.
+  std::string line;
+  int ext_google_include = 0; // 1 = warn, 2 = enable
+  int ext_google_line = 0;
+  bool had_include = false;
+  bool had_version = false;
+  int lineno = 0;
+  bool write_line_directive = (fileno != 0);
+
+  while (std::getline(in, line)) {
+    ++lineno;
+
+    if (line.empty()) {
+      // We still write a newline to make sure the line numbering remains
+      // consistent, unless we are about to write a #line directive anyway.
+      if (!write_line_directive) {
+        out.put('\n');
+      }
+      continue;
+    }
+
+    // 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);
+      std::string line2;
+
+      if (std::getline(in, line2)) {
+        line += line2;
+        if (!write_line_directive) {
+          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.
+      std::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 == std::string::npos) {
+        // Didn't find it - look in the next line.
+        if (std::getline(in, line2)) {
+          if (!write_line_directive) {
+            out.put('\n');
+          }
+          ++lineno;
+          block_end = line2.find("*/");
+        } else {
+          shader_cat.error()
+            << "Expected */ before end of file " << fn << "\n";
+          return false;
+        }
+      }
+
+      line += line2.substr(block_end + 2);
+    }
+
+    // Strip trailing whitespace.
+    while (!line.empty() && isspace(line[line.size() - 1])) {
+      line.resize(line.size() - 1);
+    }
+
+    if (line.empty()) {
+      if (!write_line_directive) {
+        out.put('\n');
+      }
+      continue;
+    }
+
+    // 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.
+      if (write_line_directive) {
+        out << "#line " << lineno << " " << fileno << " // " << fn << "\n";
+        write_line_directive = false;
+      }
+      out << line << "\n";
+      continue;
+    }
+
+    char pragma[64];
+    size_t 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[^\"]\" %zn", 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[^\"]> %zn", 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_include(module, 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.
+        write_line_directive = true;
+        had_include = true;
+        continue;
+
+      } else if (strcmp(pragma, "once") == 0) {
+        // Do a stricter syntax check, just to be extra safe.
+        if (sscanf(line.c_str(), " # pragma%*[ \t]once %zn", &nread) != 0 ||
+            nread != line.size()) {
+          shader_cat.error()
+            << "Malformed #pragma once at line " << lineno
+            << " of file " << fn << ":\n  " << line << "\n";
+          return false;
+        }
+
+        if (fileno == 0) {
+          shader_cat.warning()
+            << "#pragma once in main file at line "
+            << lineno << " of file " << fn
+#ifndef NDEBUG
+            << ":\n  " << line
+#endif
+            << "\n";
+        }
+
+        if (!full_fn.empty()) {
+          once_files.insert(full_fn);
+        }
+        continue;
+      }
+      // Otherwise, just pass it through to the driver.
+
+    } 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.
+      if (had_include) {
+        write_line_directive = true;
+      }
+
+    } else if (strcmp(directive, "version") == 0) {
+      had_version = true;
+
+    } else if (strcmp(directive, "extension") == 0) {
+      // Check for special preprocessing extensions.
+      char extension[256];
+      char behavior[9];
+      if (sscanf(line.c_str(), " # extension%*[ \t]%255[^: \t] : %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;
+          // Still pass it through to the driver, so it can enable other
+          // extensions.
+
+        } 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;
+          continue;
+
+        } else if (strcmp(extension, "GL_GOOGLE_cpp_style_line_directive") == 0) {
+          // Enables strings in #line statements.
+          ext_google_line = mode;
+          continue;
+        }
+      } 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[^\"]\" %zn", 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_include(module, 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.
+      write_line_directive = true;
+      had_include = true;
+      continue;
+
+    } 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[^\"]\" %zn", &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 = module->add_included_file(filestr);
+        out << "#line " << lineno << " " << fileno << " // " << filestr << "\n";
+        continue;
+      }
+    }
+
+    if (write_line_directive) {
+      out << "#line " << lineno << " " << fileno << " // " << fn << "\n";
+      write_line_directive = false;
+    }
+    out << line << "\n";
+  }
+
+  if (fileno == 0 && !had_version) {
+    shader_cat.warning()
+      << "GLSL shader " << fn << " does not contain a #version line!\n";
+  }
+
+  return true;
+}
+
+
+/**
+ * Loads a given GLSL file line by line, and processes any #pragma include and
+ * once statements, as well as removes any comments.
+ *
+ * The set keeps track of which files we have already included, for checking
+ * recursive includes.
+ */
+bool ShaderCompilerGlslPreProc::
+r_preprocess_include(ShaderModuleGlsl *module,
+                     std::ostream &out, const std::string &fn,
+                     const Filename &source_dir,
+                     std::set<Filename> &once_files,
+                     BamCacheRecord *record, int depth) const {
+
+  if (depth > glsl_include_recursion_limit) {
+    shader_cat.error()
+      << "GLSL includes nested too deeply, raise glsl-include-recursion-limit"
+         " if necessary\n";
+    return false;
+  }
+
+  DSearchPath path(get_model_path());
+  if (!source_dir.empty()) {
+    path.prepend_directory(source_dir);
+  }
+
+  VirtualFileSystem *vfs = VirtualFileSystem::get_global_ptr();
+  PT(VirtualFile) vf = vfs->find_file(fn, path);
+  if (vf == nullptr) {
+    shader_cat.error()
+      << "Could not find shader include: " << fn << "\n";
+    return false;
+  }
+
+  Filename full_fn = vf->get_filename();
+  if (once_files.find(full_fn) != once_files.end()) {
+    // If this file had a #pragma once, just move on.
+    return true;
+  }
+
+  std::istream *source = vf->open_read_file(true);
+  if (source == nullptr) {
+    shader_cat.error()
+      << "Could not open shader include: " << fn << "\n";
+    return false;
+  }
+
+  if (record != nullptr) {
+    record->add_dependent_file(vf);
+  }
+  //module->_source_modified = std::max(module->_source_modified, vf->get_timestamp());
+  //module->_source_files.push_back(full_fn);
+
+  // We give each file an unique index.  This is so that we can identify a
+  // particular shader in the error output.  We offset them by 2048 so that
+  // they are more recognizable.  GLSL doesn't give us anything more useful
+  // than that, unfortunately.  Don't do this for the top-level file, though.
+  // We don't want anything to get in before a potential #version directive.
+  //
+  // Write it into the vector so that we can substitute it later when we are
+  // parsing the GLSL error log.  Don't store the full filename because it
+  // would just be too long to display.
+  int fileno = module->add_included_file(fn);
+
+  if (shader_cat.is_debug()) {
+    shader_cat.debug()
+      << "Preprocessing shader include " << fileno << ": " << fn << "\n";
+  }
+
+  bool result = r_preprocess_source(module, out, *source, fn, full_fn, once_files, record, fileno, depth);
+  vf->close_read_file(source);
+  return result;
 }
 }

+ 18 - 2
panda/src/shaderpipeline/shaderCompilerGlslPreProc.h

@@ -18,6 +18,8 @@
 
 
 #include "shaderCompiler.h"
 #include "shaderCompiler.h"
 
 
+class ShaderModuleGlsl;
+
 /**
 /**
  * This defines the compiler interface to read GLSL files and pre-process them
  * This defines the compiler interface to read GLSL files and pre-process them
  */
  */
@@ -25,10 +27,24 @@ class EXPCL_PANDA_SHADERPIPELINE ShaderCompilerGlslPreProc : public ShaderCompil
 public:
 public:
   ShaderCompilerGlslPreProc();
   ShaderCompilerGlslPreProc();
 
 
-PUBLISHED:
   virtual std::string get_name() const override;
   virtual std::string get_name() const override;
   virtual ShaderLanguages get_languages() const override;
   virtual ShaderLanguages get_languages() const override;
-  virtual PT(ShaderModule) compile_now(Stage stage, std::istream &in) const override;
+  virtual PT(ShaderModule) compile_now(Stage stage, std::istream &in,
+                                       const std::string &filename = "created-shader",
+                                       BamCacheRecord *record = nullptr) const override;
+
+private:
+  bool r_preprocess_include(ShaderModuleGlsl *module,
+                            std::ostream &out, const std::string &filename,
+                            const Filename &source_dir,
+                            std::set<Filename> &open_files,
+                            BamCacheRecord *record, int depth) const;
+  bool r_preprocess_source(ShaderModuleGlsl *module,
+                           std::ostream &out, std::istream &in,
+                           const std::string &fn, const Filename &full_fn,
+                           std::set<Filename> &open_files,
+                           BamCacheRecord *record,
+                           int fileno = 0, int depth = 0) const;
 
 
 public:
 public:
   static TypeHandle get_class_type() {
   static TypeHandle get_class_type() {

+ 0 - 2
panda/src/shaderpipeline/shaderModule.cxx

@@ -55,8 +55,6 @@ fillin(DatagramIterator &scan, BamReader *manager) {
 std::string ShaderModule::
 std::string ShaderModule::
 format_stage(Stage stage) {
 format_stage(Stage stage) {
   switch (stage) {
   switch (stage) {
-  case Stage::unspecified:
-    return "unspecified";
   case Stage::vertex:
   case Stage::vertex:
     return "vertex";
     return "vertex";
   case Stage::tess_control:
   case Stage::tess_control:

+ 11 - 5
panda/src/shaderpipeline/shaderModule.h

@@ -15,14 +15,16 @@
 #define SHADERMODULE_H
 #define SHADERMODULE_H
 
 
 #include "typedWritableReferenceCount.h"
 #include "typedWritableReferenceCount.h"
+#include "bamCacheRecord.h"
 
 
 /**
 /**
- * This is the base class for the outputs of shaderCompilers
+ * Represents a single shader module in some intermediate representation for
+ * passing to the driver.  This could contain compiled bytecode, or in some
+ * cases, preprocessed source code to be given directly to the driver.
  */
  */
 class EXPCL_PANDA_SHADERPIPELINE ShaderModule : public TypedWritableReferenceCount {
 class EXPCL_PANDA_SHADERPIPELINE ShaderModule : public TypedWritableReferenceCount {
 PUBLISHED:
 PUBLISHED:
   enum class Stage {
   enum class Stage {
-    unspecified,
     vertex,
     vertex,
     tess_control,
     tess_control,
     tess_evaluation,
     tess_evaluation,
@@ -47,10 +49,14 @@ PUBLISHED:
 
 
   virtual std::string get_ir() const=0;
   virtual std::string get_ir() const=0;
 
 
-private:
-  Stage _stage = Stage::unspecified;
+protected:
+  Stage _stage;
+  PT(BamCacheRecord) _record;
+  //std::pvector<Filename> _source_files;
   Filename _source_filename;
   Filename _source_filename;
-  time_t _source_modified = 0;
+  //time_t _source_modified = 0;
+
+  friend class Shader;
 
 
 public:
 public:
   virtual void write_datagram(BamWriter *manager, Datagram &dg) override;
   virtual void write_datagram(BamWriter *manager, Datagram &dg) override;

+ 12 - 0
panda/src/shaderpipeline/shaderModuleGlsl.cxx

@@ -31,6 +31,18 @@ get_ir() const {
   return this->_raw_source;
   return this->_raw_source;
 }
 }
 
 
+/**
+ * Lists the given filename as having been included by this shader.  A unique
+ * number identifying the file is returned that can later be passed to
+ * get_filename_from_index.
+ */
+int ShaderModuleGlsl::
+add_included_file(Filename fn) {
+  int fileno = 2048 + _included_files.size();
+  _included_files.push_back(std::move(fn));
+  return fileno;
+}
+
 /**
 /**
  * Returns the filename of the included shader with the given source file
  * Returns the filename of the included shader with the given source file
  * index (as recorded in the #line statement in r_preprocess_source).  We use
  * index (as recorded in the #line statement in r_preprocess_source).  We use

+ 2 - 1
panda/src/shaderpipeline/shaderModuleGlsl.h

@@ -26,6 +26,7 @@ public:
 
 
   virtual std::string get_ir() const override;
   virtual std::string get_ir() const override;
 
 
+  int add_included_file(Filename fn);
   Filename get_filename_from_index(int index) const;
   Filename get_filename_from_index(int index) const;
 
 
 protected:
 protected:
@@ -35,7 +36,7 @@ protected:
   typedef pvector<Filename> Filenames;
   typedef pvector<Filename> Filenames;
   Filenames _included_files;
   Filenames _included_files;
 
 
-  friend class Shader;
+  friend class ShaderCompilerGlslPreProc;
 
 
 public:
 public:
   static TypeHandle get_class_type() {
   static TypeHandle get_class_type() {

+ 1 - 1
tests/shaderpipeline/test_shadercompiler.py

@@ -1,6 +1,6 @@
 from panda3d import core
 from panda3d import core
 
 
-GLSL_VERT_SHADER = ""
+GLSL_VERT_SHADER = "\n"
 
 
 def test_shadercompiler_glsl(registry):
 def test_shadercompiler_glsl(registry):
     compiler = registry.get_compiler_from_language(core.Shader.SL_GLSL)
     compiler = registry.get_compiler_from_language(core.Shader.SL_GLSL)

+ 6 - 6
tests/shaderpipeline/test_shadercompilerregistry.py

@@ -5,14 +5,14 @@ def test_shadercompilerregistry_exists(registry):
     assert registry is not None
     assert registry is not None
 
 
 
 
-def test_shadercompilerregistry_glslpreproc_loaded(registry):
-    assert core.ShaderCompilerGlslPreProc in [type(i) for i in registry.compilers]
-    assert registry.get_compiler_from_language(core.Shader.SL_GLSL) is not None
+#def test_shadercompilerregistry_glslpreproc_loaded(registry):
+#    assert core.ShaderCompilerGlslPreProc in [type(i) for i in registry.compilers]
+#    assert registry.get_compiler_from_language(core.Shader.SL_GLSL) is not None
 
 
 def test_shadercompilerregistry_cg_loaded(registry):
 def test_shadercompilerregistry_cg_loaded(registry):
     assert core.ShaderCompilerCg in [type(i) for i in registry.compilers]
     assert core.ShaderCompilerCg in [type(i) for i in registry.compilers]
     assert registry.get_compiler_from_language(core.Shader.SL_Cg) is not None
     assert registry.get_compiler_from_language(core.Shader.SL_Cg) is not None
 
 
-def test_shadercompilerregistry_missing_lang(registry):
-    assert core.ShaderCompilerGlslPreProc in [type(i) for i in registry.compilers]
-    assert registry.get_compiler_from_language(core.Shader.SL_none) is None
+#def test_shadercompilerregistry_missing_lang(registry):
+#    assert core.ShaderCompilerGlslPreProc in [type(i) for i in registry.compilers]
+#    assert registry.get_compiler_from_language(core.Shader.SL_none) is None