Ver Fonte

shaderpipeline: Implement caching, serialization

rdb há 4 anos atrás
pai
commit
7b2c330316

+ 3 - 4
panda/src/glstuff/glShaderContext_src.cxx

@@ -3399,7 +3399,7 @@ report_program_errors(GLuint program, bool fatal) {
  * Compiles the given ShaderModuleGlsl and attaches it to the program.
  * Compiles the given ShaderModuleGlsl and attaches it to the program.
  */
  */
 bool CLP(ShaderContext)::
 bool CLP(ShaderContext)::
-attach_shader(const ShaderModule *module) {
+attach_shader(const ShaderModule *module, Shader::ModuleSpecConstants &consts) {
   ShaderModule::Stage stage = module->get_stage();
   ShaderModule::Stage stage = module->get_stage();
 
 
   GLuint handle = 0;
   GLuint handle = 0;
@@ -3504,7 +3504,6 @@ attach_shader(const ShaderModule *module) {
                                 spv->get_data_size() * sizeof(uint32_t));
                                 spv->get_data_size() * sizeof(uint32_t));
       }
       }
 
 
-      const Shader::ModuleSpecConstants &consts = _shader->_module_spec_consts[module];
       _glgsg->_glSpecializeShader(handle, "main", consts._indices.size(),
       _glgsg->_glSpecializeShader(handle, "main", consts._indices.size(),
                                   (GLuint *)consts._indices.data(),
                                   (GLuint *)consts._indices.data(),
                                   (GLuint *)consts._values.data());
                                   (GLuint *)consts._values.data());
@@ -3694,8 +3693,8 @@ compile_and_link() {
 
 
   bool valid = true;
   bool valid = true;
 
 
-  for (COWPT(ShaderModule) const &cow_module : _shader->_modules) {
-    valid &= attach_shader(cow_module.get_read_pointer());
+  for (Shader::LinkedModule &linked_module : _shader->_modules) {
+    valid &= attach_shader(linked_module._module.get_read_pointer(), linked_module._consts);
   }
   }
 
 
   if (!valid) {
   if (!valid) {

+ 1 - 1
panda/src/glstuff/glShaderContext_src.h

@@ -138,7 +138,7 @@ private:
 
 
   void report_shader_errors(const Module &module, bool fatal);
   void report_shader_errors(const Module &module, bool fatal);
   void report_program_errors(GLuint program, bool fatal);
   void report_program_errors(GLuint program, bool fatal);
-  bool attach_shader(const ShaderModule *module);
+  bool attach_shader(const ShaderModule *module, Shader::ModuleSpecConstants &spec_consts);
   bool compile_and_link();
   bool compile_and_link();
   void release_resources();
   void release_resources();
 
 

+ 1 - 0
panda/src/gobj/config_gobj.cxx

@@ -636,6 +636,7 @@ ConfigureFn(config_gobj) {
   ParamTextureSampler::register_with_read_factory();
   ParamTextureSampler::register_with_read_factory();
   PerspectiveLens::register_with_read_factory();
   PerspectiveLens::register_with_read_factory();
   Shader::register_with_read_factory();
   Shader::register_with_read_factory();
+  ShaderType::register_with_read_factory();
   SliderTable::register_with_read_factory();
   SliderTable::register_with_read_factory();
   Texture::register_with_read_factory();
   Texture::register_with_read_factory();
   TextureStage::register_with_read_factory();
   TextureStage::register_with_read_factory();

+ 2 - 2
panda/src/gobj/shader.I

@@ -181,7 +181,7 @@ INLINE CPT(ShaderModule) Shader::
 get_module(Stage stage) const {
 get_module(Stage stage) const {
   if (has_stage(stage)) {
   if (has_stage(stage)) {
     size_t index = ::count_bits_in_word(((1u << (uint32_t)stage) - 1) & _module_mask);
     size_t index = ::count_bits_in_word(((1u << (uint32_t)stage) - 1) & _module_mask);
-    CPT(ShaderModule) module = _modules[index].get_read_pointer();
+    CPT(ShaderModule) module = _modules[index]._module.get_read_pointer();
     nassertr(module->get_stage() == stage, nullptr);
     nassertr(module->get_stage() == stage, nullptr);
     return module;
     return module;
   }
   }
@@ -195,7 +195,7 @@ INLINE PT(ShaderModule) Shader::
 modify_module(Stage stage) {
 modify_module(Stage stage) {
   if (has_stage(stage)) {
   if (has_stage(stage)) {
     size_t index = ::count_bits_in_word(((1u << (uint32_t)stage) - 1) & _module_mask);
     size_t index = ::count_bits_in_word(((1u << (uint32_t)stage) - 1) & _module_mask);
-    PT(ShaderModule) module = _modules[index].get_write_pointer();
+    PT(ShaderModule) module = _modules[index]._module.get_write_pointer();
     nassertr(module->get_stage() == stage, nullptr);
     nassertr(module->get_stage() == stage, nullptr);
     return module;
     return module;
   }
   }

+ 230 - 120
panda/src/gobj/shader.cxx

@@ -52,8 +52,6 @@ static bool has_cg_header(const std::string &shader_text) {
 Shader::
 Shader::
 Shader(ShaderLanguage lang) :
 Shader(ShaderLanguage lang) :
   _error_flag(false),
   _error_flag(false),
-  _parse(0),
-  _loaded(false),
   _language(lang),
   _language(lang),
   _mat_deps(0),
   _mat_deps(0),
   _cache_compiled_shader(false)
   _cache_compiled_shader(false)
@@ -603,6 +601,9 @@ bool Shader::
 read(const ShaderFile &sfile, BamCacheRecord *record) {
 read(const ShaderFile &sfile, BamCacheRecord *record) {
   _text._separate = sfile._separate;
   _text._separate = sfile._separate;
 
 
+  PT(BamCacheRecord) record2;
+  BamCache *cache;
+
   if (sfile._separate) {
   if (sfile._separate) {
     if (_language == SL_none) {
     if (_language == SL_none) {
       shader_cat.error()
       shader_cat.error()
@@ -636,8 +637,8 @@ read(const ShaderFile &sfile, BamCacheRecord *record) {
       return false;
       return false;
     }
     }
     _filename = sfile;
     _filename = sfile;
-
-  } else if (_language == SL_Cg || _language == SL_none) {
+  }
+  else if (_language == SL_Cg || _language == SL_none) {
     // For historical reasons, we have to open up this file early to determine
     // For historical reasons, we have to open up this file early to determine
     // some things about it.
     // some things about it.
     Filename fn = sfile._shared;
     Filename fn = sfile._shared;
@@ -649,15 +650,34 @@ read(const ShaderFile &sfile, BamCacheRecord *record) {
       return false;
       return false;
     }
     }
 
 
-    ShaderFile sbody;
-    sbody._separate = false;
-    if (!vf->read_file(sbody._shared, true)) {
+    // Single-file shaders are cached in their linked form in one big file.
+    Filename fullpath = vf->get_filename();
+    cache = BamCache::get_global_ptr();
+    if (cache->get_cache_compiled_shaders()) {
+      record2 = cache->lookup(fullpath, "sho");
+      if (record2 != nullptr && record2->has_data()) {
+        PT(Shader) shader = DCAST(Shader, record2->get_data());
+
+        if (!shader->_modules.empty()) {
+          shader_cat.info()
+            << "Shader " << fn << " found in disk cache.\n";
+
+          *this = *shader;
+          _debug_name = fullpath.get_basename();
+          _prepare_shader_pcollector = PStatCollector(std::string("Draw:Prepare:Shader:") + _debug_name);
+          return true;
+        }
+      }
+    }
+
+    std::string source;
+    if (!vf->read_file(source, true)) {
       shader_cat.error()
       shader_cat.error()
         << "Could not read shader file: " << fn << "\n";
         << "Could not read shader file: " << fn << "\n";
       return false;
       return false;
     }
     }
 
 
-    if (_language == SL_none && !has_cg_header(sbody._shared)) {
+    if (_language == SL_none && !has_cg_header(source)) {
       shader_cat.error()
       shader_cat.error()
         << "Unable to determine shader language of " << fn << "\n";
         << "Unable to determine shader language of " << fn << "\n";
       return false;
       return false;
@@ -665,23 +685,37 @@ read(const ShaderFile &sfile, BamCacheRecord *record) {
     _language = SL_Cg;
     _language = SL_Cg;
     _filename = sfile;
     _filename = sfile;
 
 
-    if (!do_read_source(Stage::vertex, sfile._shared, record)) {
+    shader_cat.info()
+      << "Compiling Cg shader: " << fn << "\n";
+
+    ShaderCompiler *compiler = get_compiler(SL_Cg);
+    nassertr(compiler != nullptr, false);
+
+    std::istringstream in(source);
+
+    PT(ShaderModule) module;
+    module = compiler->compile_now(Stage::vertex, in, fullpath, record);
+    if (module == nullptr || !add_module(std::move(module))) {
       return false;
       return false;
     }
     }
-    if (!do_read_source(Stage::fragment, sfile._shared, record)) {
-      return false;
+    if (source.find("gshader") != string::npos) {
+      in.clear();
+      in.seekg(0);
+      module = compiler->compile_now(Stage::geometry, in, fullpath, record);
+      if (module == nullptr || !add_module(std::move(module))) {
+        return false;
+      }
     }
     }
-    if (sbody._shared.find("gshader") != string::npos &&
-        !do_read_source(Stage::geometry, sfile._shared, record)) {
+    in.clear();
+    in.seekg(0);
+    module = compiler->compile_now(Stage::fragment, in, fullpath, record);
+    if (module == nullptr || !add_module(std::move(module))) {
       return false;
       return false;
     }
     }
 
 
-    //FIXME
-    //for (ShaderModule *module : _modules) {
-    //  module->set_source_filename(fn);
-    //}
-
-  } else {
+    _debug_name = fullpath.get_basename();
+  }
+  else {
     shader_cat.error()
     shader_cat.error()
       << "GLSL shaders must have separate shader bodies!\n";
       << "GLSL shaders must have separate shader bodies!\n";
     return false;
     return false;
@@ -693,7 +727,14 @@ read(const ShaderFile &sfile, BamCacheRecord *record) {
 
 
   _prepare_shader_pcollector = PStatCollector(std::string("Draw:Prepare:Shader:") + _debug_name);
   _prepare_shader_pcollector = PStatCollector(std::string("Draw:Prepare:Shader:") + _debug_name);
 
 
-  _loaded = true;
+  if (record2 != nullptr) {
+    // Note that we will call link() again after loading from the cache, but
+    // putting this here will make sure that we checked that it links correctly
+    // before we write it to the cache.
+    record2->set_data(this, this);
+    cache->store(record2);
+  }
+
   return true;
   return true;
 }
 }
 
 
@@ -772,8 +813,6 @@ load(const ShaderFile &sbody, BamCacheRecord *record) {
 
 
   _debug_name = "created-shader";
   _debug_name = "created-shader";
   _prepare_shader_pcollector = PStatCollector("Draw:Prepare:Shader:created-shader");
   _prepare_shader_pcollector = PStatCollector("Draw:Prepare:Shader:created-shader");
-
-  _loaded = true;
   return true;
   return true;
 }
 }
 
 
@@ -785,49 +824,23 @@ 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) {
-  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;
-  }
+  ShaderCompiler *compiler = get_compiler(_language);
+  nassertr(compiler != nullptr, false);
 
 
-  std::istream *in = vf->open_read_file(true);
-  if (in == nullptr) {
-    shader_cat.error()
-      << "Could not open shader file for reading: " << fn << "\n";
+  PT(ShaderModule) module = compiler->compile_now(stage, fn, record);
+  if (!module) {
     return false;
     return false;
   }
   }
+  nassertr(stage == module->get_stage(), false);
 
 
-  Filename fullpath = vf->get_filename();
-
-  PT(BamCacheRecord) record_pt;
-  if (record == nullptr) {
-    BamCache *cache = BamCache::get_global_ptr();
-    record_pt = cache->lookup(fullpath, "smo");
-    record = record_pt.p();
-  }
-
-  if (record != nullptr) {
-    record->add_dependent_file(vf);
-  }
-
-  shader_cat.info() << "Reading shader file: " << fn << "\n";
-  if (!do_read_source(stage, *in, fullpath, record)) {
-    vf->close_read_file(in);
+  if (!add_module(module)) {
     return false;
     return false;
   }
   }
 
 
-  //_last_modified = std::max(_last_modified, vf->get_timestamp());
-  //_source_files.push_back(fullpath);
-
-  vf->close_read_file(in);
-
   if (!_debug_name.empty()) {
   if (!_debug_name.empty()) {
     _debug_name += '/';
     _debug_name += '/';
   }
   }
-  _debug_name += fullpath.get_basename();
+  _debug_name += fn.get_basename();
 
 
   return true;
   return true;
 }
 }
@@ -849,53 +862,9 @@ do_read_source(ShaderModule::Stage stage, std::istream &in,
   if (!module) {
   if (!module) {
     return false;
     return false;
   }
   }
+  nassertr(stage == module->get_stage(), false);
 
 
-  if (!fullpath.empty()) {
-    module->set_source_filename(fullpath);
-  }
-
-  if (has_stage(stage)) {
-    shader_cat.error()
-      << "Shader already has a module with stage " << stage << ".\n";
-    return false;
-  }
-
-  if (_module_mask > (1u << (uint32_t)stage)) {
-    shader_cat.error()
-      << "Shader modules must be loaded in increasing stage order.\n";
-    return false;
-  }
-
-  // Link its inputs up with the previous stage.
-  if (!_modules.empty()) {
-    if (!module->link_inputs(_modules.back().get_read_pointer())) {
-      shader_cat.error()
-        << "Unable to match shader module interfaces.\n";
-      return false;
-    }
-  }
-  else if (stage == Stage::vertex) {
-    // Bind vertex inputs right away.
-    bool success = true;
-    for (const ShaderModule::Variable &var : module->_inputs) {
-      if (!bind_vertex_input(var.name, var.type, var.get_location())) {
-        success = false;
-      }
-    }
-    if (!success) {
-      shader_cat.error()
-        << "Failed to bind vertex inputs.\n";
-      return false;
-    }
-  }
-
-  int used_caps = module->get_used_capabilities();
-  _module_spec_consts.insert({module, ModuleSpecConstants()});
-  _modules.push_back(std::move(module));
-  _module_mask |= (1u << (uint32_t)stage);
-  _used_caps |= used_caps;
-
-  return true;
+  return add_module(module);
 }
 }
 
 
 /**
 /**
@@ -916,6 +885,8 @@ do_load_source(ShaderModule::Stage stage, const std::string &source, BamCacheRec
  */
  */
 bool Shader::
 bool Shader::
 link() {
 link() {
+  nassertr(!_modules.empty(), false);
+
   // Go through all the modules to fetch the parameters.
   // Go through all the modules to fetch the parameters.
   pmap<CPT_InternalName, Parameter> parameters_by_name;
   pmap<CPT_InternalName, Parameter> parameters_by_name;
   pvector<Parameter *> parameters;
   pvector<Parameter *> parameters;
@@ -923,8 +894,8 @@ link() {
 
 
   pmap<CPT_InternalName, const ::ShaderType *> spec_const_types;
   pmap<CPT_InternalName, const ::ShaderType *> spec_const_types;
 
 
-  for (COWPT(ShaderModule) &cow_module : _modules) {
-    const ShaderModule *module = cow_module.get_read_pointer();
+  for (LinkedModule &linked_module : _modules) {
+    const ShaderModule *module = linked_module._module.get_read_pointer();
     pmap<int, int> remap;
     pmap<int, int> remap;
 
 
     for (const ShaderModule::Variable &var : module->_parameters) {
     for (const ShaderModule::Variable &var : module->_parameters) {
@@ -981,7 +952,7 @@ link() {
     if (!remap.empty()) {
     if (!remap.empty()) {
       // We need to remap some locations.  Grab a writable pointer.  This will
       // We need to remap some locations.  Grab a writable pointer.  This will
       // make a unique copy of the module if it's also used by other shaders.
       // make a unique copy of the module if it's also used by other shaders.
-      PT(ShaderModule) module = cow_module.get_write_pointer();
+      PT(ShaderModule) module = linked_module._module.get_write_pointer();
       if (shader_cat.is_debug()) {
       if (shader_cat.is_debug()) {
         std::ostream &out = shader_cat.debug()
         std::ostream &out = shader_cat.debug()
           << "Remapping locations for module " << *module << ":";
           << "Remapping locations for module " << *module << ":";
@@ -2719,8 +2690,8 @@ bind_parameter(const Parameter &param) {
  */
  */
 bool Shader::
 bool Shader::
 check_modified() const {
 check_modified() const {
-  for (COWPT(ShaderModule) const &cow_module : _modules) {
-    const ShaderModule *module = cow_module.get_read_pointer();
+  for (const LinkedModule &linked_module : _modules) {
+    const ShaderModule *module = linked_module._module.get_read_pointer();
 
 
     if (module->_record != nullptr && !module->_record->dependents_unchanged()) {
     if (module->_record != nullptr && !module->_record->dependents_unchanged()) {
       return true;
       return true;
@@ -3036,6 +3007,67 @@ make_compute(ShaderLanguage lang, string body) {
   return shader;
   return shader;
 }
 }
 
 
+/**
+ * Adds a module to a shader. Returns true if it was added successfully, false
+ * if there was a problem. Modules must be added in increasing stage order and
+ * only one module of a given stage type may be added.
+ *
+ * If the ShaderModule is already used in a different Shader object, this will
+ * create a unique copy of it so that it may be modified to make it compatible
+ * with the other modules in this shader.
+ */
+bool Shader::
+add_module(PT(ShaderModule) module) {
+  nassertr(module != nullptr, false);
+
+  Stage stage = module->get_stage();
+  if (has_stage(stage)) {
+    shader_cat.error()
+      << "Shader already has a module with stage " << stage << ".\n";
+    return false;
+  }
+
+  if (_module_mask > (1u << (uint32_t)stage)) {
+    shader_cat.error()
+      << "Shader modules must be loaded in increasing stage order.\n";
+    return false;
+  }
+
+  // Make sure we have a unique copy.
+  COWPT(ShaderModule) cow_module = std::move(module);
+  module = cow_module.get_write_pointer();
+
+  // Link its inputs up with the previous stage.
+  if (!_modules.empty()) {
+    if (!module->link_inputs(_modules.back()._module.get_read_pointer())) {
+      shader_cat.error()
+        << "Unable to match shader module interfaces.\n";
+      return false;
+    }
+  }
+  else if (stage == Stage::vertex) {
+    // Bind vertex inputs right away.
+    bool success = true;
+    for (const ShaderModule::Variable &var : module->_inputs) {
+      if (!bind_vertex_input(var.name, var.type, var.get_location())) {
+        success = false;
+      }
+    }
+    if (!success) {
+      shader_cat.error()
+        << "Failed to bind vertex inputs.\n";
+      return false;
+    }
+  }
+
+  int used_caps = module->get_used_capabilities();
+  module = nullptr;
+  _modules.push_back(std::move(cow_module));
+  _module_mask |= (1u << (uint32_t)stage);
+  _used_caps |= used_caps;
+  return true;
+}
+
 /**
 /**
  * Sets an unsigned integer value for the specialization constant with the
  * Sets an unsigned integer value for the specialization constant with the
  * indicated name.  All modules containing a specialization constant with
  * indicated name.  All modules containing a specialization constant with
@@ -3050,14 +3082,13 @@ set_constant(CPT_InternalName name, unsigned int value) {
   bool any_found = false;
   bool any_found = false;
 
 
   // Set the value on all modules containing a spec constant with this name.
   // Set the value on all modules containing a spec constant with this name.
-  for (COWPT(ShaderModule) &cow_module : _modules) {
-    const ShaderModule *module = cow_module.get_read_pointer();
+  for (LinkedModule &linked_module : _modules) {
+    const ShaderModule *module = linked_module._module.get_read_pointer();
 
 
     for (const ShaderModule::SpecializationConstant &spec_const : module->_spec_constants) {
     for (const ShaderModule::SpecializationConstant &spec_const : module->_spec_constants) {
       if (spec_const.name == name) {
       if (spec_const.name == name) {
         // Found one.
         // Found one.
-        ModuleSpecConstants &constants = _module_spec_consts[module];
-        if (constants.set_constant(spec_const.id, value)) {
+        if (linked_module._consts.set_constant(spec_const.id, value)) {
           any_changed = true;
           any_changed = true;
         }
         }
         any_found = true;
         any_found = true;
@@ -3238,12 +3269,23 @@ register_with_read_factory() {
 void Shader::
 void Shader::
 write_datagram(BamWriter *manager, Datagram &dg) {
 write_datagram(BamWriter *manager, Datagram &dg) {
   dg.add_uint8(_language);
   dg.add_uint8(_language);
-  dg.add_bool(_loaded);
-  _filename.write_datagram(dg);
-  _text.write_datagram(dg);
 
 
   dg.add_uint32(_compiled_format);
   dg.add_uint32(_compiled_format);
   dg.add_string(_compiled_binary);
   dg.add_string(_compiled_binary);
+
+  dg.add_uint32(_module_mask);
+
+  for (const LinkedModule &linked_module : _modules) {
+    CPT(ShaderModule) module = linked_module._module.get_read_pointer();
+
+    Filename fn = module->get_source_filename();
+    dg.add_string(fn);
+
+    if (fn.empty()) {
+      // This module was not read from a file, so write the module itself too.
+      manager->write_pointer(dg, module);
+    }
+  }
 }
 }
 
 
 /**
 /**
@@ -3253,13 +3295,62 @@ write_datagram(BamWriter *manager, Datagram &dg) {
  */
  */
 TypedWritable *Shader::
 TypedWritable *Shader::
 make_from_bam(const FactoryParams &params) {
 make_from_bam(const FactoryParams &params) {
-  Shader *attrib = new Shader(SL_none);
+  Shader *shader = new Shader(SL_none);
   DatagramIterator scan;
   DatagramIterator scan;
   BamReader *manager;
   BamReader *manager;
 
 
   parse_params(params, scan, manager);
   parse_params(params, scan, manager);
-  attrib->fillin(scan, manager);
-  return attrib;
+  shader->fillin(scan, manager);
+
+  manager->register_finalize(shader);
+
+  return shader;
+}
+
+/**
+ * Receives an array of pointers, one for each time manager->read_pointer()
+ * was called in fillin(). Returns the number of pointers processed.
+ */
+int Shader::
+complete_pointers(TypedWritable **p_list, BamReader *manager) {
+  int pi = TypedWritableReferenceCount::complete_pointers(p_list, manager);
+
+  if (_modules.empty()) {
+    int num_modules = count_bits_in_word(_module_mask);
+    _module_mask = 0u;
+
+    for (int i = 0; i < num_modules; ++i) {
+      add_module(DCAST(ShaderModule, p_list[pi++]));
+    }
+  }
+
+  return pi;
+}
+
+/**
+ * Some objects require all of their nested pointers to have been completed
+ * before the objects themselves can be completed.  If this is the case,
+ * override this method to return true, and be careful with circular
+ * references (which would make the object unreadable from a bam file).
+ */
+bool Shader::
+require_fully_complete() const {
+  return true;
+}
+
+/**
+ * Called by the BamReader to perform any final actions needed for setting up
+ * the object after all objects have been read and all pointers have been
+ * completed.
+ */
+void Shader::
+finalize(BamReader *manager) {
+  // Since the shader modules may have changed since last time, we have to
+  // re-link the shader (which also binds all of the parameters).
+  if (!link()) {
+    _modules.clear();
+    _module_mask = 0u;
+  }
 }
 }
 
 
 /**
 /**
@@ -3268,11 +3359,30 @@ make_from_bam(const FactoryParams &params) {
  */
  */
 void Shader::
 void Shader::
 fillin(DatagramIterator &scan, BamReader *manager) {
 fillin(DatagramIterator &scan, BamReader *manager) {
-  _language = (ShaderLanguage) scan.get_uint8();
-  _loaded = scan.get_bool();
-  _filename.read_datagram(scan);
-  _text.read_datagram(scan);
+  _language = (ShaderLanguage)scan.get_uint8();
+  _debug_name = std::string();
+  _module_mask = 0u;
+  _modules.clear();
 
 
   _compiled_format = scan.get_uint32();
   _compiled_format = scan.get_uint32();
   _compiled_binary = scan.get_string();
   _compiled_binary = scan.get_string();
+
+  uint32_t mask = scan.get_uint32();
+  _module_mask = mask;
+  int num_modules = count_bits_in_word(mask);
+
+  for (int i = 0; i < num_modules; ++i) {
+    Stage stage = (Stage)get_lowest_on_bit(mask);
+    mask &= ~(1u << (uint32_t)stage);
+
+    Filename fn = scan.get_string();
+    if (!fn.empty()) {
+      // Compile this module from a source file.
+      do_read_source(stage, fn, nullptr);
+    }
+    else {
+      // This module was embedded.
+      manager->read_pointer(scan);
+    }
+  }
 }
 }

+ 14 - 8
panda/src/gobj/shader.h

@@ -117,6 +117,7 @@ PUBLISHED:
   INLINE bool has_stage(Stage stage) const;
   INLINE bool has_stage(Stage stage) const;
   INLINE CPT(ShaderModule) get_module(Stage stage) const;
   INLINE CPT(ShaderModule) get_module(Stage stage) const;
   INLINE PT(ShaderModule) modify_module(Stage stage);
   INLINE PT(ShaderModule) modify_module(Stage stage);
+  bool add_module(PT(ShaderModule) module);
 
 
   INLINE bool get_cache_compiled_shader() const;
   INLINE bool get_cache_compiled_shader() const;
   INLINE void set_cache_compiled_shader(bool flag);
   INLINE void set_cache_compiled_shader(bool flag);
@@ -481,8 +482,6 @@ public:
   void cp_add_mat_spec(ShaderMatSpec &spec);
   void cp_add_mat_spec(ShaderMatSpec &spec);
   size_t cp_get_mat_cache_size() const;
   size_t cp_get_mat_cache_size() const;
 
 
-  bool compile_parameter(Parameter &p);
-
   void clear_parameters();
   void clear_parameters();
 
 
   void set_compiled(unsigned int format, const char *data, size_t length);
   void set_compiled(unsigned int format, const char *data, size_t length);
@@ -512,18 +511,21 @@ public:
   bool _error_flag;
   bool _error_flag;
   ShaderFile _text;
   ShaderFile _text;
 
 
-  typedef pvector<COWPT(ShaderModule)> Modules;
+  struct LinkedModule {
+    LinkedModule(COWPT(ShaderModule) module) : _module(std::move(module)) {}
+
+    COWPT(ShaderModule) _module;
+    ModuleSpecConstants _consts;
+  };
+
+  typedef pvector<LinkedModule> Modules;
   Modules _modules;
   Modules _modules;
-  typedef pmap<const ShaderModule *, ModuleSpecConstants> ModuleSpecConsts;
-  ModuleSpecConsts _module_spec_consts;
   uint32_t _module_mask = 0;
   uint32_t _module_mask = 0;
   int _used_caps = 0;
   int _used_caps = 0;
 
 
 protected:
 protected:
   ShaderFile _filename;
   ShaderFile _filename;
   Filename _fullpath;
   Filename _fullpath;
-  int _parse;
-  bool _loaded;
   ShaderLanguage _language;
   ShaderLanguage _language;
 
 
   typedef pvector<Filename> Filenames;
   typedef pvector<Filename> Filenames;
@@ -570,7 +572,11 @@ public:
 
 
 public:
 public:
   static void register_with_read_factory();
   static void register_with_read_factory();
-  virtual void write_datagram(BamWriter *manager, Datagram &dg);
+  virtual void write_datagram(BamWriter *manager, Datagram &dg) override;
+  virtual int complete_pointers(TypedWritable **plist, BamReader *manager) override;
+  virtual bool require_fully_complete() const override;
+
+  virtual void finalize(BamReader *manager) override;
 
 
 protected:
 protected:
   static TypedWritable *make_from_bam(const FactoryParams &params);
   static TypedWritable *make_from_bam(const FactoryParams &params);

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

@@ -83,6 +83,7 @@ void ShaderModule::
 write_datagram(BamWriter *manager, Datagram &dg) {
 write_datagram(BamWriter *manager, Datagram &dg) {
   dg.add_uint8((int)_stage);
   dg.add_uint8((int)_stage);
   dg.add_string(_source_filename);
   dg.add_string(_source_filename);
+  dg.add_uint64(_used_caps);
 }
 }
 
 
 /**
 /**
@@ -93,6 +94,7 @@ void ShaderModule::
 fillin(DatagramIterator &scan, BamReader *manager) {
 fillin(DatagramIterator &scan, BamReader *manager) {
   _stage = (Stage)scan.get_uint8();
   _stage = (Stage)scan.get_uint8();
   _source_filename = scan.get_string();
   _source_filename = scan.get_string();
+  _used_caps = (int)scan.get_uint64();
 }
 }
 
 
 /**
 /**

+ 8 - 0
panda/src/gobj/shaderType.I

@@ -30,6 +30,14 @@ register_type(Type &&type) {
   return new_type;
   return new_type;
 }
 }
 
 
+/**
+ * Returns true if this type has been registered.
+ */
+INLINE bool ShaderType::
+is_registered() const {
+  return _registered_types != nullptr && _registered_types->count(this) > 0;
+}
+
 /**
 /**
  * Provides an arbitrary ordering among all unique shader types, so we can
  * Provides an arbitrary ordering among all unique shader types, so we can
  * store the essentially different ones in a big set and throw away the rest.
  * store the essentially different ones in a big set and throw away the rest.

+ 304 - 3
panda/src/gobj/shaderType.cxx

@@ -57,9 +57,8 @@ init_type() {
     _registered_types = new Registry;
     _registered_types = new Registry;
   }
   }
 
 
-  TypedReferenceCount::init_type();
-  ::register_type(_type_handle, "ShaderType",
-                  TypedReferenceCount::get_class_type());
+  TypedWritable::init_type();
+  ::register_type(_type_handle, "ShaderType", TypedWritable::get_class_type());
 
 
   ::register_type(Scalar::_type_handle, "ShaderType::Scalar", _type_handle);
   ::register_type(Scalar::_type_handle, "ShaderType::Scalar", _type_handle);
   ::register_type(Vector::_type_handle, "ShaderType::Vector", _type_handle);
   ::register_type(Vector::_type_handle, "ShaderType::Vector", _type_handle);
@@ -79,6 +78,56 @@ init_type() {
   sampler_type = ShaderType::register_type(ShaderType::Sampler());
   sampler_type = ShaderType::register_type(ShaderType::Sampler());
 }
 }
 
 
+/**
+ * Tells the BamReader how to create objects of type ShaderType.
+ */
+void ShaderType::
+register_with_read_factory() {
+  WritableFactory *factory = BamReader::get_factory();
+  factory->register_factory(Scalar::_type_handle, Scalar::make_from_bam);
+  factory->register_factory(Vector::_type_handle, Vector::make_from_bam);
+  factory->register_factory(Matrix::_type_handle, Matrix::make_from_bam);
+  factory->register_factory(Struct::_type_handle, Struct::make_from_bam);
+  factory->register_factory(Array::_type_handle, Array::make_from_bam);
+  factory->register_factory(Image::_type_handle, Image::make_from_bam);
+  factory->register_factory(Sampler::_type_handle, Sampler::make_from_bam);
+  factory->register_factory(SampledImage::_type_handle, SampledImage::make_from_bam);
+}
+
+/**
+ * Some objects require all of their nested pointers to have been completed
+ * before the objects themselves can be completed.  If this is the case,
+ * override this method to return true, and be careful with circular
+ * references (which would make the object unreadable from a bam file).
+ */
+bool ShaderType::
+require_fully_complete() const {
+  return true;
+}
+
+/**
+ * Called immediately after complete_pointers(), this gives the object a
+ * chance to adjust its own pointer if desired.  Most objects don't change
+ * pointers after completion, but some need to.
+ *
+ * Once this function has been called, the old pointer will no longer be
+ * accessed.
+ */
+TypedWritable *ShaderType::
+change_this(TypedWritable *old_ptr, BamReader *manager) {
+  nassertr(_registered_types != nullptr, old_ptr);
+
+  ShaderType *old_type = (ShaderType *)old_ptr;
+  Registry::iterator it = _registered_types->find(old_type);
+  if (it != _registered_types->end()) {
+    delete old_type;
+    return (ShaderType *)*it;
+  }
+
+  _registered_types->insert(old_type);
+  return old_type;
+}
+
 /**
 /**
  * Outputs a string description of the ScalarType to the stream.
  * Outputs a string description of the ScalarType to the stream.
  */
  */
@@ -162,6 +211,31 @@ get_align_bytes() const {
   return (_scalar_type == ST_double) ? 8 : 4;
   return (_scalar_type == ST_double) ? 8 : 4;
 }
 }
 
 
+/**
+ * Writes the contents of this object to the datagram for shipping out to a
+ * Bam file.
+ */
+void ShaderType::Scalar::
+write_datagram(BamWriter *manager, Datagram &dg) {
+  dg.add_uint8(_scalar_type);
+}
+
+/**
+ * This function is called by the BamReader's factory when a new object of
+ * type ShaderType is encountered in the Bam file.  It should create the
+ * ShaderType and extract its information from the file.
+ */
+TypedWritable *ShaderType::Scalar::
+make_from_bam(const FactoryParams &params) {
+  DatagramIterator scan;
+  BamReader *manager;
+
+  parse_params(params, scan, manager);
+
+  ScalarType scalar_type = (ScalarType)scan.get_uint8();
+  return (ShaderType *)ShaderType::register_type(ShaderType::Scalar(scalar_type));
+}
+
 /**
 /**
  * Returns true if this type contains the given scalar type.
  * Returns true if this type contains the given scalar type.
  */
  */
@@ -228,6 +302,33 @@ get_align_bytes() const {
   return component_align * ((_num_components == 3) ? 4 : _num_components);
   return component_align * ((_num_components == 3) ? 4 : _num_components);
 }
 }
 
 
+/**
+ * Writes the contents of this object to the datagram for shipping out to a
+ * Bam file.
+ */
+void ShaderType::Vector::
+write_datagram(BamWriter *manager, Datagram &dg) {
+  dg.add_uint8(_scalar_type);
+  dg.add_uint32(_num_components);
+}
+
+/**
+ * This function is called by the BamReader's factory when a new object of
+ * type ShaderType is encountered in the Bam file.  It should create the
+ * ShaderType and extract its information from the file.
+ */
+TypedWritable *ShaderType::Vector::
+make_from_bam(const FactoryParams &params) {
+  DatagramIterator scan;
+  BamReader *manager;
+
+  parse_params(params, scan, manager);
+
+  ScalarType scalar_type = (ScalarType)scan.get_uint8();
+  uint32_t num_components = scan.get_uint32();
+  return (ShaderType *)ShaderType::register_type(ShaderType::Vector(scalar_type, num_components));
+}
+
 /**
 /**
  * Returns true if this type contains the given scalar type.
  * Returns true if this type contains the given scalar type.
  */
  */
@@ -294,6 +395,35 @@ get_num_interface_locations() const {
   return _num_rows;
   return _num_rows;
 }
 }
 
 
+/**
+ * Writes the contents of this object to the datagram for shipping out to a
+ * Bam file.
+ */
+void ShaderType::Matrix::
+write_datagram(BamWriter *manager, Datagram &dg) {
+  dg.add_uint8(_scalar_type);
+  dg.add_uint32(_num_rows);
+  dg.add_uint32(_num_columns);
+}
+
+/**
+ * This function is called by the BamReader's factory when a new object of
+ * type ShaderType is encountered in the Bam file.  It should create the
+ * ShaderType and extract its information from the file.
+ */
+TypedWritable *ShaderType::Matrix::
+make_from_bam(const FactoryParams &params) {
+  DatagramIterator scan;
+  BamReader *manager;
+
+  parse_params(params, scan, manager);
+
+  ScalarType scalar_type = (ScalarType)scan.get_uint8();
+  uint32_t num_rows = scan.get_uint32();
+  uint32_t num_columns = scan.get_uint32();
+  return (ShaderType *)ShaderType::register_type(ShaderType::Matrix(scalar_type, num_rows, num_columns));
+}
+
 /**
 /**
  * Adds a member to this struct.
  * Adds a member to this struct.
  */
  */
@@ -425,6 +555,65 @@ get_num_parameter_locations() const {
   return total;
   return total;
 }
 }
 
 
+/**
+ * Writes the contents of this object to the datagram for shipping out to a
+ * Bam file.
+ */
+void ShaderType::Struct::
+write_datagram(BamWriter *manager, Datagram &dg) {
+  dg.add_uint32(_members.size());
+  for (const Member &member : _members) {
+    manager->write_pointer(dg, member.type);
+    dg.add_string(member.name);
+    dg.add_uint32(member.offset);
+  }
+}
+
+/**
+ * Receives an array of pointers, one for each time manager->read_pointer()
+ * was called in fillin(). Returns the number of pointers processed.
+ */
+int ShaderType::Struct::
+complete_pointers(TypedWritable **p_list, BamReader *manager) {
+  int pi = ShaderType::complete_pointers(p_list, manager);
+
+  for (Member &member : _members) {
+    member.type = (ShaderType *)p_list[pi++];
+    nassertd(member.type->is_registered()) continue;
+  }
+
+  return pi;
+}
+
+/**
+ * This function is called by the BamReader's factory when a new object of
+ * type ShaderType is encountered in the Bam file.  It should create the
+ * ShaderType and extract its information from the file.
+ */
+TypedWritable *ShaderType::Struct::
+make_from_bam(const FactoryParams &params) {
+  DatagramIterator scan;
+  BamReader *manager;
+
+  parse_params(params, scan, manager);
+
+  ShaderType::Struct *struct_type = new ShaderType::Struct;
+
+  size_t num_members = scan.get_uint32();
+  struct_type->_members.resize(num_members);
+  for (size_t i = 0; i < num_members; ++i) {
+    manager->read_pointer(scan);
+
+    Member &member = struct_type->_members[i];
+    member.type = nullptr;
+    member.name = scan.get_string();
+    member.offset = scan.get_uint32();
+  }
+
+  manager->register_change_this(change_this, struct_type);
+  return struct_type;
+}
+
 /**
 /**
  * If this type is an array, puts the element type in the first argument and the
  * If this type is an array, puts the element type in the first argument and the
  * number of elements in the second argument, and returns true.  If not, puts
  * number of elements in the second argument, and returns true.  If not, puts
@@ -528,6 +717,48 @@ get_num_parameter_locations() const {
   return _element_type->get_num_parameter_locations() * _num_elements;
   return _element_type->get_num_parameter_locations() * _num_elements;
 }
 }
 
 
+/**
+ * Writes the contents of this object to the datagram for shipping out to a
+ * Bam file.
+ */
+void ShaderType::Array::
+write_datagram(BamWriter *manager, Datagram &dg) {
+  manager->write_pointer(dg, _element_type);
+  dg.add_uint32(_num_elements);
+}
+
+/**
+ * Receives an array of pointers, one for each time manager->read_pointer()
+ * was called in fillin(). Returns the number of pointers processed.
+ */
+int ShaderType::Array::
+complete_pointers(TypedWritable **p_list, BamReader *manager) {
+  int pi = ShaderType::complete_pointers(p_list, manager);
+  _element_type = (ShaderType *)p_list[pi++];
+  nassertr(_element_type->is_registered(), pi);
+  return pi;
+}
+
+/**
+ * This function is called by the BamReader's factory when a new object of
+ * type ShaderType is encountered in the Bam file.  It should create the
+ * ShaderType and extract its information from the file.
+ */
+TypedWritable *ShaderType::Array::
+make_from_bam(const FactoryParams &params) {
+  DatagramIterator scan;
+  BamReader *manager;
+
+  parse_params(params, scan, manager);
+
+  manager->read_pointer(scan);
+  uint32_t num_elements = scan.get_uint32();
+  ShaderType *type = new ShaderType::Array(nullptr, num_elements);
+
+  manager->register_change_this(change_this, type);
+  return type;
+}
+
 /**
 /**
  *
  *
  */
  */
@@ -560,6 +791,36 @@ compare_to_impl(const ShaderType &other) const {
        - (_access < other_image._access);
        - (_access < other_image._access);
 }
 }
 
 
+/**
+ * Writes the contents of this object to the datagram for shipping out to a
+ * Bam file.
+ */
+void ShaderType::Image::
+write_datagram(BamWriter *manager, Datagram &dg) {
+  dg.add_uint8(_texture_type);
+  dg.add_uint8(_sampled_type);
+  dg.add_uint8((uint8_t)_access);
+}
+
+/**
+ * This function is called by the BamReader's factory when a new object of
+ * type ShaderType is encountered in the Bam file.  It should create the
+ * ShaderType and extract its information from the file.
+ */
+TypedWritable *ShaderType::Image::
+make_from_bam(const FactoryParams &params) {
+  DatagramIterator scan;
+  BamReader *manager;
+
+  parse_params(params, scan, manager);
+
+  Texture::TextureType texture_type = (Texture::TextureType)scan.get_uint8();
+  ScalarType sampled_type = (ScalarType)scan.get_uint8();
+  Access access = (Access)scan.get_uint8();
+
+  return (ShaderType *)ShaderType::register_type(ShaderType::Image(texture_type, sampled_type, access));
+}
+
 /**
 /**
  *
  *
  */
  */
@@ -578,6 +839,16 @@ compare_to_impl(const ShaderType &other) const {
   return true;
   return true;
 }
 }
 
 
+/**
+ * This function is called by the BamReader's factory when a new object of
+ * type ShaderType is encountered in the Bam file.  It should create the
+ * ShaderType and extract its information from the file.
+ */
+TypedWritable *ShaderType::Sampler::
+make_from_bam(const FactoryParams &params) {
+  return (ShaderType *)ShaderType::sampler_type;
+}
+
 /**
 /**
  *
  *
  */
  */
@@ -612,4 +883,34 @@ compare_to_impl(const ShaderType &other) const {
   return (_shadow > other_sampled_image._shadow)
   return (_shadow > other_sampled_image._shadow)
        - (_shadow < other_sampled_image._shadow);
        - (_shadow < other_sampled_image._shadow);
 }
 }
+
+/**
+ * Writes the contents of this object to the datagram for shipping out to a
+ * Bam file.
+ */
+void ShaderType::SampledImage::
+write_datagram(BamWriter *manager, Datagram &dg) {
+  dg.add_uint8(_texture_type);
+  dg.add_uint8(_sampled_type);
+  dg.add_bool(_shadow);
+}
+
+/**
+ * This function is called by the BamReader's factory when a new object of
+ * type ShaderType is encountered in the Bam file.  It should create the
+ * ShaderType and extract its information from the file.
+ */
+TypedWritable *ShaderType::SampledImage::
+make_from_bam(const FactoryParams &params) {
+  DatagramIterator scan;
+  BamReader *manager;
+
+  parse_params(params, scan, manager);
+
+  Texture::TextureType texture_type = (Texture::TextureType)scan.get_uint8();
+  ScalarType sampled_type = (ScalarType)scan.get_uint8();
+  bool shadow = scan.get_bool();
+  return (ShaderType *)ShaderType::register_type(ShaderType::SampledImage(texture_type, sampled_type, shadow));
+}
+
 #endif  // CPPPARSER
 #endif  // CPPPARSER

+ 38 - 0
panda/src/gobj/shaderType.h

@@ -28,6 +28,7 @@ class EXPCL_PANDA_GOBJ ShaderType : public TypedWritable {
 public:
 public:
   template<class Type>
   template<class Type>
   static const Type *register_type(Type &&type);
   static const Type *register_type(Type &&type);
+  INLINE bool is_registered() const;
 
 
   INLINE int compare_to(const ShaderType &other) const;
   INLINE int compare_to(const ShaderType &other) const;
   virtual int compare_to_impl(const ShaderType &other) const=0;
   virtual int compare_to_impl(const ShaderType &other) const=0;
@@ -88,6 +89,10 @@ public:
   virtual const Sampler *as_sampler() const { return nullptr; }
   virtual const Sampler *as_sampler() const { return nullptr; }
   virtual const SampledImage *as_sampled_image() const { return nullptr; }
   virtual const SampledImage *as_sampled_image() const { return nullptr; }
 
 
+  static void register_with_read_factory();
+  virtual bool require_fully_complete() const;
+  static TypedWritable *change_this(TypedWritable *old_ptr, BamReader *manager);
+
 public:
 public:
   static TypeHandle get_class_type() {
   static TypeHandle get_class_type() {
     return _type_handle;
     return _type_handle;
@@ -135,6 +140,10 @@ private:
 
 
   const ScalarType _scalar_type;
   const ScalarType _scalar_type;
 
 
+protected:
+  virtual void write_datagram(BamWriter *manager, Datagram &dg) override;
+  static TypedWritable *make_from_bam(const FactoryParams &params);
+
 public:
 public:
   static TypeHandle get_class_type() {
   static TypeHandle get_class_type() {
     return _type_handle;
     return _type_handle;
@@ -178,6 +187,10 @@ private:
   const ScalarType _scalar_type;
   const ScalarType _scalar_type;
   const uint32_t _num_components;
   const uint32_t _num_components;
 
 
+protected:
+  virtual void write_datagram(BamWriter *manager, Datagram &dg) override;
+  static TypedWritable *make_from_bam(const FactoryParams &params);
+
 public:
 public:
   static TypeHandle get_class_type() {
   static TypeHandle get_class_type() {
     return _type_handle;
     return _type_handle;
@@ -222,6 +235,10 @@ private:
   const uint32_t _num_rows;
   const uint32_t _num_rows;
   const uint32_t _num_columns;
   const uint32_t _num_columns;
 
 
+protected:
+  virtual void write_datagram(BamWriter *manager, Datagram &dg) override;
+  static TypedWritable *make_from_bam(const FactoryParams &params);
+
 public:
 public:
   static TypeHandle get_class_type() {
   static TypeHandle get_class_type() {
     return _type_handle;
     return _type_handle;
@@ -272,6 +289,11 @@ PUBLISHED:
 private:
 private:
   pvector<Member> _members;
   pvector<Member> _members;
 
 
+protected:
+  virtual void write_datagram(BamWriter *manager, Datagram &dg) override;
+  virtual int complete_pointers(TypedWritable **plist, BamReader *manager);
+  static TypedWritable *make_from_bam(const FactoryParams &params);
+
 public:
 public:
   static TypeHandle get_class_type() {
   static TypeHandle get_class_type() {
     return _type_handle;
     return _type_handle;
@@ -322,6 +344,11 @@ private:
   const ShaderType *_element_type;
   const ShaderType *_element_type;
   uint32_t _num_elements;
   uint32_t _num_elements;
 
 
+protected:
+  virtual void write_datagram(BamWriter *manager, Datagram &dg) override;
+  virtual int complete_pointers(TypedWritable **plist, BamReader *manager);
+  static TypedWritable *make_from_bam(const FactoryParams &params);
+
 public:
 public:
   static TypeHandle get_class_type() {
   static TypeHandle get_class_type() {
     return _type_handle;
     return _type_handle;
@@ -372,6 +399,10 @@ private:
   ScalarType _sampled_type;
   ScalarType _sampled_type;
   Access _access;
   Access _access;
 
 
+protected:
+  virtual void write_datagram(BamWriter *manager, Datagram &dg) override;
+  static TypedWritable *make_from_bam(const FactoryParams &params);
+
 public:
 public:
   static TypeHandle get_class_type() {
   static TypeHandle get_class_type() {
     return _type_handle;
     return _type_handle;
@@ -399,6 +430,9 @@ public:
 
 
   const Sampler *as_sampler() const override { return this; }
   const Sampler *as_sampler() const override { return this; }
 
 
+protected:
+  static TypedWritable *make_from_bam(const FactoryParams &params);
+
 public:
 public:
   static TypeHandle get_class_type() {
   static TypeHandle get_class_type() {
     return _type_handle;
     return _type_handle;
@@ -435,6 +469,10 @@ private:
   ScalarType _sampled_type;
   ScalarType _sampled_type;
   bool _shadow = false;
   bool _shadow = false;
 
 
+protected:
+  virtual void write_datagram(BamWriter *manager, Datagram &dg) override;
+  static TypedWritable *make_from_bam(const FactoryParams &params);
+
 public:
 public:
   static TypeHandle get_class_type() {
   static TypeHandle get_class_type() {
     return _type_handle;
     return _type_handle;

+ 3 - 3
panda/src/putil/bamCache.cxx

@@ -77,10 +77,10 @@ BamCache() :
               "model-cache-textures, or it may be independent."));
               "model-cache-textures, or it may be independent."));
 
 
   ConfigVariableBool model_cache_compiled_shaders
   ConfigVariableBool model_cache_compiled_shaders
-    ("model-cache-compiled-shaders", false,
+    ("model-cache-compiled-shaders", true,
      PRC_DESC("If this is set to true, compiled shaders will be cached "
      PRC_DESC("If this is set to true, compiled shaders will be cached "
-              "in the model cache, in their binary form as downloaded "
-              "by the GSG."));
+              "in the model cache, in their binary form as compiled by the "
+              "shader compiler."));
 
 
   ConfigVariableInt model_cache_max_kbytes
   ConfigVariableInt model_cache_max_kbytes
     ("model-cache-max-kbytes", 10485760,
     ("model-cache-max-kbytes", 10485760,

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

@@ -48,6 +48,8 @@ init_libshaderpipeline() {
   ShaderModuleSpirV::init_type();
   ShaderModuleSpirV::init_type();
   ShaderModuleGlsl::init_type();
   ShaderModuleGlsl::init_type();
 
 
+  ShaderModuleSpirV::register_with_read_factory();
+
   ShaderCompilerRegistry *reg = ShaderCompilerRegistry::get_global_ptr();
   ShaderCompilerRegistry *reg = ShaderCompilerRegistry::get_global_ptr();
   reg->register_compiler(new ShaderCompilerGlslang());
   reg->register_compiler(new ShaderCompilerGlslang());
   reg->register_compiler(new ShaderCompilerGlslPreProc());
   reg->register_compiler(new ShaderCompilerGlslPreProc());

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

@@ -14,8 +14,20 @@
 #include "shaderCompiler.h"
 #include "shaderCompiler.h"
 #include "virtualFileSystem.h"
 #include "virtualFileSystem.h"
 #include "config_putil.h"
 #include "config_putil.h"
+#include "bamCache.h"
 #include "bamCacheRecord.h"
 #include "bamCacheRecord.h"
 
 
+// Use different model cache extensions for different stages, since the same
+// shader file might have been loaded more than once for different stages.
+const char *const cache_extensions[] = {
+  "vert.smo",
+  "tesc.smo",
+  "tese.smo",
+  "geom.smo",
+  "frag.smo",
+  "comp.smo",
+};
+
 TypeHandle ShaderCompiler::_type_handle;
 TypeHandle ShaderCompiler::_type_handle;
 
 
 /**
 /**
@@ -45,6 +57,25 @@ compile_now(ShaderModule::Stage stage, const Filename &fn, BamCacheRecord *recor
       << "Could not find shader file: " << fn << "\n";
       << "Could not find shader file: " << fn << "\n";
     return nullptr;
     return nullptr;
   }
   }
+  if (record != nullptr) {
+    record->add_dependent_file(vf);
+  }
+
+  // Try to read from the compiled shader module cache first.
+  Filename fullpath = vf->get_filename();
+  BamCache *cache = BamCache::get_global_ptr();
+
+  PT(BamCacheRecord) record2;
+  if (cache->get_cache_compiled_shaders()) {
+    record2 = cache->lookup(fullpath, cache_extensions[(int)stage]);
+    if (record2 != nullptr && record2->has_data()) {
+      PT(ShaderModule) module = DCAST(ShaderModule, record2->get_data());
+
+      shader_cat.info()
+        << "Shader module " << fn << " found in disk cache.\n";
+      return module;
+    }
+  }
 
 
   std::istream *in = vf->open_read_file(true);
   std::istream *in = vf->open_read_file(true);
   if (vf == nullptr) {
   if (vf == nullptr) {
@@ -53,8 +84,22 @@ compile_now(ShaderModule::Stage stage, const Filename &fn, BamCacheRecord *recor
     return nullptr;
     return nullptr;
   }
   }
 
 
+  shader_cat.info()
+    << "Compiling " << stage << " shader module: " << fn << "\n";
+
   // The default implementation calls the version that takes an istream.
   // The default implementation calls the version that takes an istream.
-  PT(ShaderModule) module = compile_now(stage, *in, vf->get_filename(), record);
+  PT(ShaderModule) module = compile_now(stage, *in, fullpath, record2);
   vf->close_read_file(in);
   vf->close_read_file(in);
+
+  if (module != nullptr) {
+    module->set_source_filename(fullpath);
+
+    if (record2 != nullptr && module != nullptr) {
+      // Update the compiled shader module cache.
+      record2->set_data(module, module);
+      cache->store(record2);
+    }
+  }
+
   return module;
   return module;
 }
 }

+ 1 - 1
panda/src/shaderpipeline/shaderCompilerGlslPreProc.cxx

@@ -412,7 +412,7 @@ r_preprocess_source(ShaderModuleGlsl *module,
 
 
   if (fileno == 0) {
   if (fileno == 0) {
     if (!had_code) {
     if (!had_code) {
-      shader_cat.warning()
+      shader_cat.error()
         << "GLSL shader " << fn << " does not contain any code!\n";
         << "GLSL shader " << fn << " does not contain any code!\n";
       return false;
       return false;
     }
     }

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

@@ -278,6 +278,8 @@ compile_now(ShaderModule::Stage stage, std::istream &in,
 
 
   vector_uchar code;
   vector_uchar code;
   if (!VirtualFile::simple_read_file(&in, code)) {
   if (!VirtualFile::simple_read_file(&in, code)) {
+    shader_cat.error()
+      << "Failed to read " << stage << " shader from stream.\n";
     return nullptr;
     return nullptr;
   }
   }
 
 

+ 8 - 0
panda/src/shaderpipeline/shaderModuleSpirV.I

@@ -11,6 +11,14 @@
  * @date 2020-06-01
  * @date 2020-06-01
  */
  */
 
 
+/**
+ */
+INLINE ShaderModuleSpirV::
+ShaderModuleSpirV(Stage stage) :
+  ShaderModule(stage)
+{
+}
+
 /**
 /**
  * Returns a pointer to the raw words.
  * Returns a pointer to the raw words.
  */
  */

+ 151 - 0
panda/src/shaderpipeline/shaderModuleSpirV.cxx

@@ -475,6 +475,157 @@ strip() {
   _instructions = copy;
   _instructions = copy;
 }
 }
 
 
+/**
+ * Tells the BamReader how to create objects of type ShaderModuleSpirV.
+ */
+void ShaderModuleSpirV::
+register_with_read_factory() {
+  BamReader::get_factory()->register_factory(get_class_type(), make_from_bam);
+}
+
+/**
+ * Writes the contents of this object to the datagram for shipping out to a
+ * Bam file.
+ */
+void ShaderModuleSpirV::
+write_datagram(BamWriter *manager, Datagram &dg) {
+  ShaderModule::write_datagram(manager, dg);
+
+  dg.add_uint32(_inputs.size());
+  for (const Variable &input : _inputs) {
+    manager->write_pointer(dg, input.type);
+    manager->write_pointer(dg, input.name);
+    dg.add_int32(input._location);
+  }
+
+  dg.add_uint32(_outputs.size());
+  for (const Variable &output : _outputs) {
+    manager->write_pointer(dg, output.type);
+    manager->write_pointer(dg, output.name);
+    dg.add_int32(output._location);
+  }
+
+  dg.add_uint32(_parameters.size());
+  for (const Variable &parameter : _parameters) {
+    manager->write_pointer(dg, parameter.type);
+    manager->write_pointer(dg, parameter.name);
+    dg.add_int32(parameter._location);
+  }
+
+  dg.add_uint32(_spec_constants.size());
+  for (const SpecializationConstant &spec_constant : _spec_constants) {
+    manager->write_pointer(dg, spec_constant.type);
+    manager->write_pointer(dg, spec_constant.name);
+    dg.add_uint32(spec_constant.id);
+  }
+
+  size_t num_words = _instructions.get_data_size();
+  const uint32_t *words = _instructions.get_data();
+
+  nassertv(num_words < UINT32_MAX);
+  dg.add_uint32(num_words);
+  for (size_t i = 0; i < num_words; ++i) {
+    dg.add_uint32(words[i]);
+  }
+}
+
+/**
+ * This function is called by the BamReader's factory when a new object of
+ * type ShaderModule is encountered in the Bam file.  It should create the
+ * ShaderModule and extract its information from the file.
+ */
+TypedWritable *ShaderModuleSpirV::
+make_from_bam(const FactoryParams &params) {
+  DatagramIterator scan;
+  BamReader *manager;
+
+  parse_params(params, scan, manager);
+
+  Stage stage = (Stage)scan.get_uint8();
+  ShaderModuleSpirV *module = new ShaderModuleSpirV(stage);
+  module->fillin(scan, manager);
+
+  return module;
+}
+
+/**
+ * Receives an array of pointers, one for each time manager->read_pointer()
+ * was called in fillin(). Returns the number of pointers processed.
+ */
+int ShaderModuleSpirV::
+complete_pointers(TypedWritable **p_list, BamReader *manager) {
+  int pi = ShaderModule::complete_pointers(p_list, manager);
+
+  for (Variable &input : _inputs) {
+    input.type = DCAST(ShaderType, p_list[pi++]);
+    input.name = DCAST(InternalName, p_list[pi++]);
+  }
+  for (Variable &output : _outputs) {
+    output.type = DCAST(ShaderType, p_list[pi++]);
+    output.name = DCAST(InternalName, p_list[pi++]);
+  }
+  for (Variable &parameter : _parameters) {
+    parameter.type = DCAST(ShaderType, p_list[pi++]);
+    parameter.name = DCAST(InternalName, p_list[pi++]);
+  }
+  for (SpecializationConstant &spec_constant : _spec_constants) {
+    spec_constant.type = DCAST(ShaderType, p_list[pi++]);
+    spec_constant.name = DCAST(InternalName, p_list[pi++]);
+  }
+
+  return pi;
+}
+
+/**
+ * This internal function is called by make_from_bam to read in all of the
+ * relevant data from the BamFile for the new ShaderModuleSpirV.
+ */
+void ShaderModuleSpirV::
+fillin(DatagramIterator &scan, BamReader *manager) {
+  _source_filename = scan.get_string();
+  _used_caps = (int)scan.get_uint64();
+
+  uint32_t num_inputs = scan.get_uint32();
+  _inputs.resize(num_inputs);
+  for (uint32_t i = 0; i < num_inputs; ++i) {
+    manager->read_pointer(scan); // type
+    manager->read_pointer(scan); // name
+    _inputs[i]._location = scan.get_int32();
+  }
+
+  uint32_t num_outputs = scan.get_uint32();
+  _outputs.resize(num_outputs);
+  for (uint32_t i = 0; i < num_outputs; ++i) {
+    manager->read_pointer(scan); // type
+    manager->read_pointer(scan); // name
+    _outputs[i]._location = scan.get_int32();
+  }
+
+  uint32_t num_parameters = scan.get_uint32();
+  _parameters.resize(num_parameters);
+  for (uint32_t i = 0; i < num_parameters; ++i) {
+    manager->read_pointer(scan); // type
+    manager->read_pointer(scan); // name
+    _parameters[i]._location = scan.get_int32();
+  }
+
+  uint32_t num_spec_constants = scan.get_uint32();
+  _spec_constants.resize(num_spec_constants);
+  for (uint32_t i = 0; i < num_spec_constants; ++i) {
+    manager->read_pointer(scan); // type
+    manager->read_pointer(scan); // name
+    _spec_constants[i].id = scan.get_uint32();
+  }
+
+  uint32_t num_words = scan.get_uint32();
+  std::vector<uint32_t> words(num_words);
+  for (uint32_t i = 0; i < num_words; ++i) {
+    words[i] = scan.get_uint32();
+  }
+  _instructions = std::move(words);
+  nassertv(_instructions.validate_header());
+}
+
 /**
 /**
  * Returns true if this type contains anything decorated with BuiltIn.
  * Returns true if this type contains anything decorated with BuiltIn.
  */
  */

+ 12 - 0
panda/src/shaderpipeline/shaderModuleSpirV.h

@@ -25,6 +25,9 @@ class ShaderType;
  * link the module to a previous stage, and strip debug information as needed.
  * link the module to a previous stage, and strip debug information as needed.
  */
  */
 class EXPCL_PANDA_SHADERPIPELINE ShaderModuleSpirV final : public ShaderModule {
 class EXPCL_PANDA_SHADERPIPELINE ShaderModuleSpirV final : public ShaderModule {
+private:
+  ShaderModuleSpirV(Stage stage);
+
 public:
 public:
   ShaderModuleSpirV(Stage stage, std::vector<uint32_t> words, BamCacheRecord *record = nullptr);
   ShaderModuleSpirV(Stage stage, std::vector<uint32_t> words, BamCacheRecord *record = nullptr);
   virtual ~ShaderModuleSpirV();
   virtual ~ShaderModuleSpirV();
@@ -246,6 +249,15 @@ private:
   void remap_locations(spv::StorageClass storage_class, const pmap<int, int> &locations);
   void remap_locations(spv::StorageClass storage_class, const pmap<int, int> &locations);
   void strip();
   void strip();
 
 
+public:
+  static void register_with_read_factory();
+  virtual void write_datagram(BamWriter *manager, Datagram &dg) override;
+  virtual int complete_pointers(TypedWritable **plist, BamReader *manager) override;
+
+protected:
+  static TypedWritable *make_from_bam(const FactoryParams &params);
+  void fillin(DatagramIterator &scan, BamReader *manager) override;
+
 public:
 public:
   static TypeHandle get_class_type() {
   static TypeHandle get_class_type() {
     return _type_handle;
     return _type_handle;