Преглед на файлове

shaderpipeline: Configurable compiler options/defines, type testing

rdb преди 1 месец
родител
ревизия
2935f16dad

+ 47 - 40
panda/src/gobj/shader.cxx

@@ -162,7 +162,7 @@ get_compiled(unsigned int &format, string &binary) const {
  * success or failure.
  */
 bool Shader::
-read(const ShaderFile &sfile, BamCacheRecord *record) {
+read(const ShaderFile &sfile, const CompilerOptions &options, BamCacheRecord *record) {
   _text._separate = sfile._separate;
 
   PT(BamCacheRecord) record2;
@@ -177,27 +177,27 @@ read(const ShaderFile &sfile, BamCacheRecord *record) {
 
     // Read the various stages in order.
     if (!sfile._vertex.empty() &&
-        !do_read_source(Stage::VERTEX, sfile._vertex, record)) {
+        !do_read_source(Stage::VERTEX, sfile._vertex, options, record)) {
       return false;
     }
     if (!sfile._tess_control.empty() &&
-        !do_read_source(Stage::TESS_CONTROL, sfile._tess_control, record)) {
+        !do_read_source(Stage::TESS_CONTROL, sfile._tess_control, options, record)) {
       return false;
     }
     if (!sfile._tess_evaluation.empty() &&
-        !do_read_source(Stage::TESS_EVALUATION, sfile._tess_evaluation, record)) {
+        !do_read_source(Stage::TESS_EVALUATION, sfile._tess_evaluation, options, record)) {
       return false;
     }
     if (!sfile._geometry.empty() &&
-        !do_read_source(Stage::GEOMETRY, sfile._geometry, record)) {
+        !do_read_source(Stage::GEOMETRY, sfile._geometry, options, record)) {
       return false;
     }
     if (!sfile._fragment.empty() &&
-        !do_read_source(Stage::FRAGMENT, sfile._fragment, record)) {
+        !do_read_source(Stage::FRAGMENT, sfile._fragment, options, record)) {
       return false;
     }
     if (!sfile._compute.empty() &&
-        !do_read_source(Stage::COMPUTE, sfile._compute, record)) {
+        !do_read_source(Stage::COMPUTE, sfile._compute, options, record)) {
       return false;
     }
     _filename = sfile;
@@ -256,23 +256,24 @@ read(const ShaderFile &sfile, BamCacheRecord *record) {
     ShaderCompiler *compiler = registry->get_compiler_for_language(SL_Cg);
     nassertr(compiler != nullptr, false);
 
+    CompilerOptions options;
     std::istringstream in(source);
 
-    PT(ShaderModule) vertex = compiler->compile_now(Stage::VERTEX, in, fullpath, record);
+    PT(ShaderModule) vertex = compiler->compile_now(Stage::VERTEX, in, fullpath, options, nullptr, record);
     if (vertex == nullptr || !add_module(std::move(vertex))) {
       return false;
     }
     if (source.find("gshader") != string::npos) {
       in.clear();
       in.seekg(0);
-      PT(ShaderModule) geometry = compiler->compile_now(Stage::GEOMETRY, in, fullpath, record);
+      PT(ShaderModule) geometry = compiler->compile_now(Stage::GEOMETRY, in, fullpath, options, nullptr, record);
       if (geometry == nullptr || !add_module(std::move(geometry))) {
         return false;
       }
     }
     in.clear();
     in.seekg(0);
-    PT(ShaderModule) fragment = compiler->compile_now(Stage::FRAGMENT, in, fullpath, record);
+    PT(ShaderModule) fragment = compiler->compile_now(Stage::FRAGMENT, in, fullpath, options, nullptr, record);
     if (fragment == nullptr || !add_module(std::move(fragment))) {
       return false;
     }
@@ -307,7 +308,7 @@ read(const ShaderFile &sfile, BamCacheRecord *record) {
  * success or failure.
  */
 bool Shader::
-load(const ShaderFile &sbody, BamCacheRecord *record) {
+load(const ShaderFile &sbody, const CompilerOptions &options, BamCacheRecord *record) {
   _filename = ShaderFile("created-shader");
   _fullpath = Filename();
   _text._separate = sbody._separate;
@@ -320,27 +321,27 @@ load(const ShaderFile &sbody, BamCacheRecord *record) {
     }
 
     if (!sbody._vertex.empty() &&
-        !do_load_source(Stage::VERTEX, sbody._vertex, record)) {
+        !do_load_source(Stage::VERTEX, sbody._vertex, options, record)) {
       return false;
     }
     if (!sbody._tess_control.empty() &&
-        !do_load_source(Stage::TESS_CONTROL, sbody._tess_control, record)) {
+        !do_load_source(Stage::TESS_CONTROL, sbody._tess_control, options, record)) {
       return false;
     }
     if (!sbody._tess_evaluation.empty() &&
-        !do_load_source(Stage::TESS_EVALUATION, sbody._tess_evaluation, record)) {
+        !do_load_source(Stage::TESS_EVALUATION, sbody._tess_evaluation, options, record)) {
       return false;
     }
     if (!sbody._geometry.empty() &&
-        !do_load_source(Stage::GEOMETRY, sbody._geometry, record)) {
+        !do_load_source(Stage::GEOMETRY, sbody._geometry, options, record)) {
       return false;
     }
     if (!sbody._fragment.empty() &&
-        !do_load_source(Stage::FRAGMENT, sbody._fragment, record)) {
+        !do_load_source(Stage::FRAGMENT, sbody._fragment, options, record)) {
       return false;
     }
     if (!sbody._compute.empty() &&
-        !do_load_source(Stage::COMPUTE, sbody._compute, record)) {
+        !do_load_source(Stage::COMPUTE, sbody._compute, options, record)) {
       return false;
     }
 
@@ -352,14 +353,14 @@ load(const ShaderFile &sbody, BamCacheRecord *record) {
     }
     _language = SL_Cg;
 
-    if (!do_load_source(Stage::VERTEX, sbody._shared, record)) {
+    if (!do_load_source(Stage::VERTEX, sbody._shared, options, record)) {
       return false;
     }
-    if (!do_load_source(Stage::FRAGMENT, sbody._shared, record)) {
+    if (!do_load_source(Stage::FRAGMENT, sbody._shared, options, record)) {
       return false;
     }
     if (sbody._shared.find("gshader") != string::npos &&
-        !do_load_source(Stage::GEOMETRY, sbody._shared, record)) {
+        !do_load_source(Stage::GEOMETRY, sbody._shared, options, record)) {
       return false;
     }
 
@@ -387,12 +388,12 @@ load(const ShaderFile &sbody, BamCacheRecord *record) {
  * it 'invalid'.
  */
 bool Shader::
-do_read_source(Stage stage, const Filename &fn, BamCacheRecord *record) {
+do_read_source(Stage stage, const Filename &fn, const CompilerOptions &options, BamCacheRecord *record) {
   ShaderCompilerRegistry *registry = ShaderCompilerRegistry::get_global_ptr();
   ShaderCompiler *compiler = registry->get_compiler_for_language(_language);
   nassertr(compiler != nullptr, false);
 
-  PT(ShaderModule) module = compiler->compile_now(stage, fn, record);
+  PT(ShaderModule) module = compiler->compile_now(stage, fn, options, nullptr, record);
   if (!module) {
     return false;
   }
@@ -419,12 +420,13 @@ do_read_source(Stage stage, const Filename &fn, BamCacheRecord *record) {
  */
 bool Shader::
 do_read_source(ShaderModule::Stage stage, std::istream &in,
-               const Filename &fullpath, BamCacheRecord *record) {
+               const Filename &fullpath, const CompilerOptions &options,
+               BamCacheRecord *record) {
   ShaderCompilerRegistry *registry = ShaderCompilerRegistry::get_global_ptr();
   ShaderCompiler *compiler = registry->get_compiler_for_language(_language);
   nassertr(compiler != nullptr, false);
 
-  PT(ShaderModule) module = compiler->compile_now(stage, in, fullpath, record);
+  PT(ShaderModule) module = compiler->compile_now(stage, in, fullpath, options, nullptr, record);
   if (!module) {
     return false;
   }
@@ -441,9 +443,10 @@ do_read_source(ShaderModule::Stage stage, std::istream &in,
  * it 'invalid'.
  */
 bool Shader::
-do_load_source(ShaderModule::Stage stage, const std::string &source, BamCacheRecord *record) {
+do_load_source(ShaderModule::Stage stage, const std::string &source,
+               const CompilerOptions &options, BamCacheRecord *record) {
   std::istringstream in(source);
-  return do_read_source(stage, in, Filename("created-shader"), record);
+  return do_read_source(stage, in, Filename("created-shader"), options, record);
 }
 
 /**
@@ -677,7 +680,7 @@ check_modified() const {
  * Loads the shader with the given filename.
  */
 PT(Shader) Shader::
-load(const Filename &file, SourceLanguage lang) {
+load(const Filename &file, SourceLanguage lang, const CompilerOptions &options) {
   ShaderFile sfile(file);
   ShaderTable::const_iterator i = _load_table.find(sfile);
   if (i != _load_table.end() && (lang == SL_none || lang == i->second->_language)) {
@@ -695,7 +698,7 @@ load(const Filename &file, SourceLanguage lang) {
   }
 
   PT(Shader) shader = new Shader(lang);
-  if (!shader->read(sfile)) {
+  if (!shader->read(sfile, options)) {
     return nullptr;
   }
 
@@ -717,7 +720,8 @@ load(const Filename &file, SourceLanguage lang) {
 PT(Shader) Shader::
 load(SourceLanguage lang, const Filename &vertex,
      const Filename &fragment, const Filename &geometry,
-     const Filename &tess_control, const Filename &tess_evaluation) {
+     const Filename &tess_control, const Filename &tess_evaluation,
+     const CompilerOptions &options) {
   ShaderFile sfile(vertex, fragment, geometry, tess_control, tess_evaluation);
   ShaderTable::const_iterator i = _load_table.find(sfile);
   if (i != _load_table.end() && (lang == SL_none || lang == i->second->_language)) {
@@ -735,7 +739,7 @@ load(SourceLanguage lang, const Filename &vertex,
   }
 
   PT(Shader) shader = new Shader(lang);
-  if (!shader->read(sfile)) {
+  if (!shader->read(sfile, options)) {
     return nullptr;
   }
 
@@ -755,7 +759,8 @@ load(SourceLanguage lang, const Filename &vertex,
  * Loads a compute shader.
  */
 PT(Shader) Shader::
-load_compute(SourceLanguage lang, const Filename &fn) {
+load_compute(SourceLanguage lang, const Filename &fn,
+             const CompilerOptions &options) {
   if (lang != SL_GLSL) {
     shader_cat.error()
       << "Only GLSL compute shaders are currently supported.\n";
@@ -803,7 +808,7 @@ load_compute(SourceLanguage lang, const Filename &fn) {
   }
 
   PT(Shader) shader = new Shader(lang);
-  if (!shader->read(sfile, record)) {
+  if (!shader->read(sfile, options, record)) {
     return nullptr;
   }
 
@@ -829,7 +834,7 @@ load_compute(SourceLanguage lang, const Filename &fn) {
  * Loads the shader, using the string as shader body.
  */
 PT(Shader) Shader::
-make(string body, SourceLanguage lang) {
+make(string body, SourceLanguage lang, const CompilerOptions &options) {
   if (lang == SL_GLSL) {
     shader_cat.error()
       << "GLSL shaders must have separate shader bodies!\n";
@@ -854,7 +859,7 @@ make(string body, SourceLanguage lang) {
   }
 
   PT(Shader) shader = new Shader(lang);
-  if (!shader->load(sbody)) {
+  if (!shader->load(sbody, options)) {
     return nullptr;
   }
 
@@ -888,7 +893,7 @@ make(string body, SourceLanguage lang) {
  */
 PT(Shader) Shader::
 make(SourceLanguage lang, string vertex, string fragment, string geometry,
-     string tess_control, string tess_evaluation) {
+     string tess_control, string tess_evaluation, const CompilerOptions &options) {
   if (lang == SL_none) {
     shader_cat.error()
       << "Shader::make() requires an explicit shader language.\n";
@@ -910,7 +915,7 @@ make(SourceLanguage lang, string vertex, string fragment, string geometry,
 
   PT(Shader) shader = new Shader(lang);
   shader->_filename = ShaderFile("created-shader");
-  if (!shader->load(sbody)) {
+  if (!shader->load(sbody, options)) {
     return nullptr;
   }
 
@@ -931,7 +936,7 @@ make(SourceLanguage lang, string vertex, string fragment, string geometry,
  * Loads the compute shader from the given string.
  */
 PT(Shader) Shader::
-make_compute(SourceLanguage lang, string body) {
+make_compute(SourceLanguage lang, string body, const CompilerOptions &options) {
   if (lang != SL_GLSL) {
     shader_cat.error()
       << "Only GLSL compute shaders are currently supported.\n";
@@ -954,7 +959,7 @@ make_compute(SourceLanguage lang, string body) {
 
   PT(Shader) shader = new Shader(lang);
   shader->_filename = ShaderFile("created-shader");
-  if (!shader->load(sbody)) {
+  if (!shader->load(sbody, options)) {
     return nullptr;
   }
 
@@ -1253,7 +1258,8 @@ write_datagram(BamWriter *manager, Datagram &dg) {
   for (const LinkedModule &linked_module : _modules) {
     CPT(ShaderModule) module = linked_module._module.get_read_pointer();
 
-    Filename fn = module->get_source_filename();
+    Filename fn;
+    //Filename fn = module->get_source_filename();
     dg.add_string(fn);
 
     if (fn.empty()) {
@@ -1353,7 +1359,8 @@ fillin(DatagramIterator &scan, BamReader *manager) {
     Filename fn = scan.get_string();
     if (!fn.empty()) {
       // Compile this module from a source file.
-      do_read_source(stage, fn, nullptr);
+      //FIXME: store options
+      do_read_source(stage, fn, CompilerOptions(), nullptr);
     }
     else {
       // This module was embedded.

+ 25 - 11
panda/src/gobj/shader.h

@@ -38,6 +38,7 @@
 #include "shaderModule.h"
 #include "copyOnWritePointer.h"
 #include "shaderInputBinding.h"
+#include "compilerOptions.h"
 
 class BamCacheRecord;
 class ShaderCompiler;
@@ -81,20 +82,26 @@ PUBLISHED:
 PUBLISHED:
   Shader(SourceLanguage lang);
 
-  static PT(Shader) load(const Filename &file, SourceLanguage lang = SL_none);
-  static PT(Shader) make(std::string body, SourceLanguage lang = SL_none);
+  static PT(Shader) load(const Filename &file, SourceLanguage lang = SL_none,
+                         const CompilerOptions &options = CompilerOptions());
+  static PT(Shader) make(std::string body, SourceLanguage lang = SL_none,
+                         const CompilerOptions &options = CompilerOptions());
   static PT(Shader) load(SourceLanguage lang,
                          const Filename &vertex, const Filename &fragment,
                          const Filename &geometry = "",
                          const Filename &tess_control = "",
-                         const Filename &tess_evaluation = "");
-  static PT(Shader) load_compute(SourceLanguage lang, const Filename &fn);
+                         const Filename &tess_evaluation = "",
+                         const CompilerOptions &options = CompilerOptions());
+  static PT(Shader) load_compute(SourceLanguage lang, const Filename &fn,
+                                 const CompilerOptions &options = CompilerOptions());
   static PT(Shader) make(SourceLanguage lang,
                          std::string vertex, std::string fragment,
                          std::string geometry = "",
                          std::string tess_control = "",
-                         std::string tess_evaluation = "");
-  static PT(Shader) make_compute(SourceLanguage lang, std::string body);
+                         std::string tess_evaluation = "",
+                         const CompilerOptions &options = CompilerOptions());
+  static PT(Shader) make_compute(SourceLanguage lang, std::string body,
+                                 const CompilerOptions &options = CompilerOptions());
 
   INLINE Filename get_filename(DeprecatedShaderType type = ST_none) const;
   INLINE void set_filename(DeprecatedShaderType type, const Filename &filename);
@@ -326,12 +333,19 @@ protected:
 private:
   void clear_prepared(PreparedGraphicsObjects *prepared_objects);
 
-  bool read(const ShaderFile &sfile, BamCacheRecord *record = nullptr);
-  bool load(const ShaderFile &sbody, BamCacheRecord *record = nullptr);
-  bool do_read_source(ShaderModule::Stage stage, const Filename &fn, BamCacheRecord *record);
+  bool read(const ShaderFile &sfile,
+            const CompilerOptions &options = CompilerOptions(),
+            BamCacheRecord *record = nullptr);
+  bool load(const ShaderFile &sbody,
+            const CompilerOptions &options = CompilerOptions(),
+            BamCacheRecord *record = nullptr);
+  bool do_read_source(ShaderModule::Stage stage, const Filename &fn,
+                      const CompilerOptions &options, BamCacheRecord *record);
   bool do_read_source(ShaderModule::Stage stage, std::istream &in,
-                      const Filename &fullpath, BamCacheRecord *record);
-  bool do_load_source(ShaderModule::Stage stage, const std::string &source, BamCacheRecord *record);
+                      const Filename &fullpath, const CompilerOptions &options,
+                      BamCacheRecord *record);
+  bool do_load_source(ShaderModule::Stage stage, const std::string &source,
+                      const CompilerOptions &options, BamCacheRecord *record);
 
 public:
   bool link();

+ 3 - 2
panda/src/gobj/shaderCompiler.cxx

@@ -49,7 +49,8 @@ ShaderCompiler::
  * ShaderModule on success.
  */
 PT(ShaderModule) ShaderCompiler::
-compile_now(Stage stage, const Filename &fn, BamCacheRecord *record) const {
+compile_now(Stage stage, const Filename &fn, const CompilerOptions &options,
+            std::ostream *output_log, BamCacheRecord *record) const {
   VirtualFileSystem *vfs = VirtualFileSystem::get_global_ptr();
   PT(VirtualFile) vf = vfs->find_file(fn, get_model_path());
   if (vf == nullptr) {
@@ -92,7 +93,7 @@ compile_now(Stage stage, const Filename &fn, BamCacheRecord *record) const {
     << "Compiling " << stage << " shader module: " << fn << "\n";
 
   // The default implementation calls the version that takes an istream.
-  PT(ShaderModule) module = compile_now(stage, *in, fullpath, record2);
+  PT(ShaderModule) module = compile_now(stage, *in, fullpath, options, output_log, record2);
   vf->close_read_file(in);
 
   if (module != nullptr) {

+ 11 - 1
panda/src/gobj/shaderCompiler.h

@@ -20,6 +20,7 @@
 #include "typedObject.h"
 #include "shader.h"
 #include "shaderModule.h"
+#include "compilerOptions.h"
 
 /**
  * This is the base class for objects to compile various types of shader code.
@@ -31,13 +32,22 @@ protected:
 public:
   virtual ~ShaderCompiler();
 
-PUBLISHED:
+public:
   virtual std::string get_name() const=0;
   virtual SourceLanguages get_languages() const=0;
+
+PUBLISHED:
+  MAKE_PROPERTY(name, get_name);
+  MAKE_PROPERTY(languages, get_languages);
+
   virtual PT(ShaderModule) compile_now(Stage stage, const Filename &path,
+                                       const CompilerOptions &options = CompilerOptions(),
+                                       std::ostream *output_log = nullptr,
                                        BamCacheRecord *record = nullptr) const;
   virtual PT(ShaderModule) compile_now(Stage stage, std::istream &in,
                                        const Filename &fullpath,
+                                       const CompilerOptions &options = CompilerOptions(),
+                                       std::ostream *output_log = nullptr,
                                        BamCacheRecord *record = nullptr) const=0;
 
 public:

+ 2 - 0
panda/src/putil/CMakeLists.txt

@@ -20,6 +20,7 @@ set(P3PUTIL_HEADERS
   clockObject.h clockObject.I
   collideMask.h
   colorSpace.h
+  compilerOptions.I compilerOptions.h
   completable.I completable.h
   completionCounter.I completionCounter.h
   completionToken.I completionToken.h
@@ -89,6 +90,7 @@ set(P3PUTIL_SOURCES
   callbackObject.cxx
   clockObject.cxx
   colorSpace.cxx
+  compilerOptions.cxx
   completionCounter.cxx
   copyOnWriteObject.cxx
   copyOnWritePointer.cxx

+ 84 - 0
panda/src/putil/compilerOptions.I

@@ -0,0 +1,84 @@
+/**
+ * PANDA 3D SOFTWARE
+ * Copyright (c) Carnegie Mellon University.  All rights reserved.
+ *
+ * All use of this software is subject to the terms of the revised BSD
+ * license.  You should have received a copy of this license along
+ * with this source code in a file named "LICENSE."
+ *
+ * @file compilerOptions.I
+ * @author rdb
+ * @date 2025-12-23
+ */
+
+/**
+ *
+ */
+INLINE bool CompilerOptions::
+get_suppress_log() const {
+  return (_flags & F_suppress_log) != 0;
+}
+
+/**
+ * If enabled, does not write compiler warnings/errors to the standard notify
+ * stream.
+ */
+INLINE void CompilerOptions::
+set_suppress_log(bool enabled) {
+  if (enabled) {
+    _flags |= F_suppress_log;
+  } else {
+    _flags &= ~F_suppress_log;
+  }
+}
+
+/**
+ * Returns the current optimization setting.
+ */
+INLINE CompilerOptions::Optimize CompilerOptions::
+get_optimize() const {
+  return _optimize;
+}
+
+/**
+ * Sets the focus of optimization: NONE (which does not perform optimization),
+ * PERFORMANCE (the default) or SIZE.
+ */
+INLINE void CompilerOptions::
+set_optimize(CompilerOptions::Optimize opt) {
+  _optimize = opt;
+}
+
+/**
+ *
+ */
+INLINE bool CompilerOptions::
+has_define(const std::string &key) const {
+  return _defines.find(key) != _defines.end();
+}
+
+/**
+ *
+ */
+INLINE std::string CompilerOptions::
+get_define(const std::string &key) const {
+  auto it = _defines.find(key);
+  nassertr_always(it != _defines.end(), "");
+  return it->second;
+}
+
+/**
+ * Sets a preprocessor definition.
+ */
+INLINE void CompilerOptions::
+define(const std::string &key, const std::string &value) {
+  _defines[key] = value;
+}
+
+/**
+ * Removes a preprocessor definition previously set with define().
+ */
+INLINE void CompilerOptions::
+undef(const std::string &key) {
+  _defines.erase(key);
+}

+ 41 - 0
panda/src/putil/compilerOptions.cxx

@@ -0,0 +1,41 @@
+/**
+ * PANDA 3D SOFTWARE
+ * Copyright (c) Carnegie Mellon University.  All rights reserved.
+ *
+ * All use of this software is subject to the terms of the revised BSD
+ * license.  You should have received a copy of this license along
+ * with this source code in a file named "LICENSE."
+ *
+ * @file compilerOptions.cxx
+ * @author rdb
+ * @date 2025-12-23
+ */
+
+#include "compilerOptions.h"
+#include "config_putil.h"
+#include "indent.h"
+
+using std::string;
+
+/**
+ *
+ */
+void CompilerOptions::
+output(std::ostream &out) const {
+  out << "CompilerOptions(";
+  out << ")";
+}
+
+/**
+ * Writes the definitions to the given output stream.
+ */
+void CompilerOptions::
+write_defines(std::ostream &out) const {
+  for (const auto &pair : _defines) {
+    if (pair.second.empty()) {
+      out << "#define " << pair.first << "\n";
+    } else {
+      out << "#define " << pair.first << " " << pair.second << "\n";
+    }
+  }
+}

+ 72 - 0
panda/src/putil/compilerOptions.h

@@ -0,0 +1,72 @@
+/**
+ * PANDA 3D SOFTWARE
+ * Copyright (c) Carnegie Mellon University.  All rights reserved.
+ *
+ * All use of this software is subject to the terms of the revised BSD
+ * license.  You should have received a copy of this license along
+ * with this source code in a file named "LICENSE."
+ *
+ * @file compilerOptions.h
+ * @author rdb
+ * @date 2025-12-23
+ */
+
+#ifndef COMPILEROPTIONS_H
+#define COMPILEROPTIONS_H
+
+#include "pandabase.h"
+
+/**
+ * Specifies parameters that may be passed to the shader compiler.
+ */
+class EXPCL_PANDA_PUTIL CompilerOptions {
+PUBLISHED:
+  CompilerOptions() = default;
+
+  enum class Optimize {
+    NONE = 0,
+    PERFORMANCE,
+    SIZE,
+  };
+
+public:
+  INLINE bool get_suppress_log() const;
+  INLINE void set_suppress_log(bool enabled);
+
+  INLINE Optimize get_optimize() const;
+  INLINE void set_optimize(Optimize opt);
+
+  INLINE bool has_define(const std::string &key) const;
+  INLINE std::string get_define(const std::string &key) const;
+
+PUBLISHED:
+  INLINE void define(const std::string &key, const std::string &value = "1");
+  INLINE void undef(const std::string &key);
+
+  MAKE_PROPERTY(suppress_log, get_suppress_log, set_suppress_log);
+  MAKE_PROPERTY(optimize, get_optimize, set_optimize);
+  MAKE_MAP_PROPERTY(defines, has_define, get_define, define, undef);
+
+  void output(std::ostream &out) const;
+  void write_defines(std::ostream &out) const;
+
+private:
+  enum Flags {
+    F_suppress_log       = 0x0001,
+  };
+
+  int _flags = 0;
+  Optimize _optimize = Optimize::PERFORMANCE;
+
+  typedef pmap<std::string, std::string> Defines;
+  Defines _defines;
+};
+
+INLINE std::ostream &operator << (std::ostream &out, const CompilerOptions &opts) {
+  opts.output(out);
+  return out;
+}
+
+#include "compilerOptions.I"
+
+#endif

+ 1 - 0
panda/src/putil/p3putil_composite1.cxx

@@ -17,6 +17,7 @@
 #include "callbackObject.cxx"
 #include "clockObject.cxx"
 #include "colorSpace.cxx"
+#include "compilerOptions.cxx"
 #include "completionCounter.cxx"
 #include "config_putil.cxx"
 #include "configurable.cxx"

+ 32 - 5
panda/src/shaderpipeline/shaderCompilerGlslPreProc.cxx

@@ -81,6 +81,7 @@ get_languages() const {
  */
 PT(ShaderModule) ShaderCompilerGlslPreProc::
 compile_now(Stage stage, std::istream &in, const Filename &fullpath,
+            const CompilerOptions &options, std::ostream *output_log,
             BamCacheRecord *record) const {
   // Create a name that's easier to read in error messages.
   std::string filename;
@@ -96,8 +97,18 @@ compile_now(Stage stage, std::istream &in, const Filename &fullpath,
     }
   }
 
+  std::string preamble;
+  {
+    std::ostringstream preamble_stream;
+    if (options.get_optimize() == CompilerOptions::Optimize::NONE) {
+      preamble_stream << "#pragma optimize(off)\n";
+    }
+    options.write_defines(preamble_stream);
+    preamble = std::move(preamble_stream).str();
+  }
+
   State state;
-  if (!r_preprocess_source(state, in, filename, fullpath, record)) {
+  if (!r_preprocess_source(state, in, filename, fullpath, preamble, record)) {
     return nullptr;
   }
 
@@ -106,12 +117,19 @@ compile_now(Stage stage, std::istream &in, const Filename &fullpath,
       << "GLSL shader " << filename << " does not contain any code!\n";
     return nullptr;
   }
+  std::string code = std::move(state.code).str();
+
   if (!state.version) {
     shader_cat.warning()
       << "GLSL shader " << filename << " does not contain a #version line!\n";
+
+    // If we don't have a version line, it hasn't inserted the preamble.
+    if (!preamble.empty()) {
+      code = preamble + "#line 1\n" + code;
+    }
   }
 
-  PT(ShaderModuleGlsl) module = new ShaderModuleGlsl(stage, state.code.str(), state.version);
+  PT(ShaderModuleGlsl) module = new ShaderModuleGlsl(stage, std::move(code), state.version);
   module->_included_files = std::move(state.included_files);
   module->_used_caps |= state.required_caps;
   module->_record = record;
@@ -127,8 +145,8 @@ compile_now(Stage stage, std::istream &in, const Filename &fullpath,
  */
 bool ShaderCompilerGlslPreProc::
 r_preprocess_source(State &state, std::istream &in, const std::string &fn,
-                    const Filename &full_fn, BamCacheRecord *record, int fileno,
-                    int depth) const {
+                    const Filename &full_fn, const std::string &preamble,
+                    BamCacheRecord *record, int fileno, int depth) const {
 
   // Iterate over the lines for things we may need to preprocess.
   std::string line;
@@ -316,12 +334,21 @@ r_preprocess_source(State &state, std::istream &in, const std::string &fn,
       state.cond_nesting--;
     }
     else if (strcmp(directive, "version") == 0) {
+      if (state.version != 0) {
+        shader_cat.error()
+          << "Duplicated #version at line " << lineno << " of file " << fn
+          << ": " << line << "\n";
+        return false;
+      }
       if (sscanf(line.c_str(), " # version %d", &state.version) != 1 || state.version <= 0) {
         shader_cat.error()
           << "Invalid version number at line " << lineno << " of file " << fn
           << ": " << line << "\n";
         return false;
       }
+      if (!preamble.empty()) {
+        out << preamble << "#line 1\n";
+      }
     }
     else if (strcmp(directive, "extension") == 0) {
       // Check for special preprocessing extensions.
@@ -545,7 +572,7 @@ r_preprocess_include(State &state, const std::string &fn,
       << "Preprocessing shader include " << fileno << ": " << fn << "\n";
   }
 
-  bool result = r_preprocess_source(state, *source, fn, full_fn, record, fileno, depth);
+  bool result = r_preprocess_source(state, *source, fn, full_fn, std::string(), record, fileno, depth);
   vf->close_read_file(source);
   return result;
 }

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

@@ -31,6 +31,8 @@ public:
   virtual SourceLanguages get_languages() const override;
   virtual PT(ShaderModule) compile_now(Stage stage, std::istream &in,
                                        const Filename &fullpath,
+                                       const CompilerOptions &options,
+                                       std::ostream *output_log = nullptr,
                                        BamCacheRecord *record = nullptr) const override;
 
 private:
@@ -48,8 +50,9 @@ private:
                             int depth) const;
   bool r_preprocess_source(State &state, std::istream &in,
                            const std::string &fn, const Filename &full_fn,
-                           BamCacheRecord *record = nullptr, int fileno = 0,
-                           int depth = 0) const;
+                           const std::string &preamble,
+                           BamCacheRecord *record = nullptr,
+                           int fileno = 0, int depth = 0) const;
 
 public:
   static TypeHandle get_class_type() {

+ 87 - 52
panda/src/shaderpipeline/shaderCompilerGlslang.cxx

@@ -168,7 +168,8 @@ get_languages() const {
  */
 PT(ShaderModule) ShaderCompilerGlslang::
 compile_now(ShaderModule::Stage stage, std::istream &in,
-            const Filename &fullpath, BamCacheRecord *record) const {
+            const Filename &fullpath, const CompilerOptions &options,
+            std::ostream *output_log, BamCacheRecord *record) const {
 
   vector_uchar code;
   if (!VirtualFile::simple_read_file(&in, code)) {
@@ -229,7 +230,7 @@ compile_now(ShaderModule::Stage stage, std::istream &in,
     static ShaderCompilerGlslPreProc preprocessor;
 
     std::istringstream stream(std::string((const char *)&code[0], code.size()));
-    return preprocessor.compile_now(stage, stream, fullpath, record);
+    return preprocessor.compile_now(stage, stream, fullpath, options, output_log, record);
   }
 
   static bool is_initialized = false;
@@ -274,42 +275,50 @@ compile_now(ShaderModule::Stage stage, std::istream &in,
   shader.setEntryPoint("main");
 
   // If it's marked as a Cg shader, we compile it with the HLSL front-end.
-  if (is_cg) {
-    messages = (EShMessages)(messages | EShMsgHlslDX9Compatible | EShMsgHlslLegalization);
-
-    const char *source_entry_point;
-    switch (stage) {
-    case ShaderModule::Stage::VERTEX:
-      source_entry_point = "vshader";
-      break;
-    case ShaderModule::Stage::GEOMETRY:
-      source_entry_point = "gshader";
-      break;
-    case ShaderModule::Stage::FRAGMENT:
-      source_entry_point = "fshader";
-      break;
-    default:
-      shader_cat.error()
-        << "Cg does not support " << stage << " shaders.\n";
-      return nullptr;
-    }
-    shader.setSourceEntryPoint(source_entry_point);
+  std::string preamble;
+  {
+    std::ostringstream preamble_stream;
+    if (is_cg) {
+      messages = (EShMessages)(messages | EShMsgHlslDX9Compatible | EShMsgHlslLegalization);
+
+      const char *source_entry_point;
+      switch (stage) {
+      case ShaderModule::Stage::VERTEX:
+        source_entry_point = "vshader";
+        break;
+      case ShaderModule::Stage::GEOMETRY:
+        source_entry_point = "gshader";
+        break;
+      case ShaderModule::Stage::FRAGMENT:
+        source_entry_point = "fshader";
+        break;
+      default:
+        shader_cat.error()
+          << "Cg does not support " << stage << " shaders.\n";
+        return nullptr;
+      }
+      shader.setSourceEntryPoint(source_entry_point);
 
-    // Generate a special preamble to define some functions that Cg defines but
-    // HLSL doesn't.  This is sourced from cg_preamble.hlsl.
-    extern const char cg_preamble[];
-    shader.setPreamble(cg_preamble);
+      // Generate a special preamble to define some functions that Cg defines but
+      // HLSL doesn't.  This is sourced from cg_preamble.hlsl.
+      extern const char cg_preamble[];
+      preamble_stream << cg_preamble;
 
-    // We map some sampler types to DX10 syntax, but those use separate
-    // samplers/images, so we need to ask glslang to combine these back.
-    shader.setTextureSamplerTransformMode(EShTexSampTransUpgradeTextureRemoveSampler);
+      // We map some sampler types to DX10 syntax, but those use separate
+      // samplers/images, so we need to ask glslang to combine these back.
+      shader.setTextureSamplerTransformMode(EShTexSampTransUpgradeTextureRemoveSampler);
 
-    shader.setEnvInput(glslang::EShSource::EShSourceHlsl, (EShLanguage)stage, glslang::EShClient::EShClientOpenGL, 120);
-  } else {
-    shader.setEnvInput(glslang::EShSource::EShSourceGlsl, (EShLanguage)stage, glslang::EShClient::EShClientOpenGL, 450);
+      shader.setEnvInput(glslang::EShSource::EShSourceHlsl, (EShLanguage)stage, glslang::EShClient::EShClientOpenGL, 120);
+    } else {
+      shader.setEnvInput(glslang::EShSource::EShSourceGlsl, (EShLanguage)stage, glslang::EShClient::EShClientOpenGL, 450);
 
-    shader.setPreamble("#extension GL_GOOGLE_cpp_style_line_directive : require\n");
+      preamble_stream << "#extension GL_GOOGLE_cpp_style_line_directive : require\n";
+    }
+    options.write_defines(preamble_stream);
+    preamble = std::move(preamble_stream).str();
   }
+  shader.setPreamble(preamble.c_str());
+
   shader.setEnvClient(glslang::EShClient::EShClientOpenGL, glslang::EShTargetOpenGL_450);
   shader.setEnvTarget(glslang::EShTargetSpv, glslang::EShTargetSpv_1_0);
 
@@ -320,9 +329,14 @@ compile_now(ShaderModule::Stage stage, std::istream &in,
 
   Includer includer(record);
   if (!shader.parse(GetDefaultResources(), 110, false, messages, includer)) {
-    shader_cat.error()
-      << "Failed to parse " << filename << ":\n"
-      << shader.getInfoLog();
+    if (output_log != nullptr) {
+      *output_log << shader.getInfoLog();
+    }
+    if (!options.get_suppress_log()) {
+      shader_cat.error()
+        << "Failed to parse " << filename << ":\n"
+        << shader.getInfoLog();
+    }
     return nullptr;
   }
 
@@ -354,9 +368,14 @@ compile_now(ShaderModule::Stage stage, std::istream &in,
 
     std::string messages = logger.getAllMessages();
     if (!messages.empty()) {
-      shader_cat.warning()
-        << "Compilation to SPIR-V produced the following messages:\n"
-        << messages;
+      if (output_log != nullptr) {
+        *output_log << messages;
+      }
+      if (!options.get_suppress_log()) {
+        shader_cat.warning()
+          << "Compilation to SPIR-V produced the following messages:\n"
+          << messages;
+      }
     }
   }
 
@@ -373,23 +392,39 @@ compile_now(ShaderModule::Stage stage, std::istream &in,
   }
 
   // Run it through the optimizer.
-  spvtools::Optimizer opt(SPV_ENV_UNIVERSAL_1_0);
-  opt.SetMessageConsumer(log_message);
-  opt.RegisterPerformancePasses();
+  std::vector<uint32_t> optimized;
+  if (is_cg || options.get_optimize() != CompilerOptions::Optimize::NONE) {
+    spvtools::Optimizer opt(SPV_ENV_UNIVERSAL_1_0);
+    opt.SetMessageConsumer(log_message);
 
-  if (is_cg) {
-    opt.RegisterLegalizationPasses();
-  }
+    switch (options.get_optimize()) {
+    case CompilerOptions::Optimize::NONE:
+      break;
 
-  // We skip validation because of the `uniform bool` bug, see SPIRV-Tools#3387
-  std::vector<uint32_t> optimized;
-  spvtools::ValidatorOptions validator_options;
-  if (!opt.Run(stream.get_data(), stream.get_data_size(), &optimized,
-               validator_options, true)) {
-    return nullptr;
+    case CompilerOptions::Optimize::PERFORMANCE:
+      opt.RegisterPerformancePasses();
+      break;
+
+    case CompilerOptions::Optimize::SIZE:
+      opt.RegisterSizePasses();
+      break;
+    }
+
+    if (is_cg) {
+      opt.RegisterLegalizationPasses();
+    }
+
+    // We skip validation because of the `uniform bool` bug, see SPIRV-Tools#3387
+    spvtools::ValidatorOptions validator_options;
+    if (!opt.Run(stream.get_data(), stream.get_data_size(), &optimized,
+                 validator_options, true)) {
+      return nullptr;
+    }
+  } else {
+    optimized = stream;
   }
 
-  return new ShaderModuleSpirV(stage, std::move(optimized), record);
+  return new ShaderModuleSpirV(stage, std::move(optimized), options, record);
 }
 
 /**

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

@@ -31,6 +31,8 @@ public:
   virtual SourceLanguages get_languages() const override;
   virtual PT(ShaderModule) compile_now(Stage stage, std::istream &in,
                                        const Filename &fullpath,
+                                       const CompilerOptions &options,
+                                       std::ostream *output_log = nullptr,
                                        BamCacheRecord *record = nullptr) const override;
 
 private:

+ 4 - 2
panda/src/shaderpipeline/shaderModuleSpirV.cxx

@@ -38,7 +38,7 @@ TypeHandle ShaderModuleSpirV::_type_handle;
  * - Strips debugging information from the module.
  */
 ShaderModuleSpirV::
-ShaderModuleSpirV(Stage stage, std::vector<uint32_t> words, BamCacheRecord *record) :
+ShaderModuleSpirV(Stage stage, std::vector<uint32_t> words, const CompilerOptions &options, BamCacheRecord *record) :
   ShaderModule(stage),
   _instructions(std::move(words))
 {
@@ -143,7 +143,9 @@ ShaderModuleSpirV(Stage stage, std::vector<uint32_t> words, BamCacheRecord *reco
   }
 
   // Remove unused variables before assigning locations.
-  transformer.run(SpirVRemoveUnusedVariablesPass());
+  if (options.get_optimize() != CompilerOptions::Optimize::NONE) {
+    transformer.run(SpirVRemoveUnusedVariablesPass());
+  }
 
   // Add in location decorations for any inputs that are missing it.
   transformer.assign_interface_locations(stage);

+ 4 - 1
panda/src/shaderpipeline/shaderModuleSpirV.h

@@ -24,6 +24,7 @@
 
 #include <spirv-tools/libspirv.h>
 
+class CompilerOptions;
 class ShaderType;
 
 /**
@@ -36,7 +37,9 @@ private:
   ShaderModuleSpirV(Stage stage);
 
 public:
-  ShaderModuleSpirV(Stage stage, std::vector<uint32_t> words, BamCacheRecord *record = nullptr);
+  ShaderModuleSpirV(Stage stage, std::vector<uint32_t> words,
+                    const CompilerOptions &options = CompilerOptions(),
+                    BamCacheRecord *record = nullptr);
   virtual ~ShaderModuleSpirV();
 
   virtual PT(CopyOnWriteObject) make_cow_copy() override;

+ 4 - 3
tests/display/conftest.py

@@ -216,7 +216,7 @@ class ShaderEnvironment:
         assert result
 
     def run_glsl(self, body, preamble="", inputs={}, version=420, exts=set(),
-                 state=core.RenderState.make_empty()):
+                 state=core.RenderState.make_empty(), options=None):
         """ Runs a GLSL test on the given GSG.  The given body is executed in the
         main function and should call assert().  The preamble should contain all
         of the shader inputs. """
@@ -234,6 +234,7 @@ class ShaderEnvironment:
 
         version = version or 420
         exts = exts | {'GL_ARB_compute_shader', 'GL_ARB_shader_image_load_store'}
+        options = options or core.CompilerOptions()
 
         extensions = ''
         for ext in exts:
@@ -246,11 +247,11 @@ class ShaderEnvironment:
 
         if use_compute:
             code = GLSL_COMPUTE_TEMPLATE.format(version=version, extensions=extensions, preamble=preamble, body=body)
-            shader = core.Shader.make_compute(core.Shader.SL_GLSL, code)
+            shader = core.Shader.make_compute(core.Shader.SL_GLSL, code, options=options)
         else:
             vertex_code = GLSL_VERTEX_TEMPLATE.format(version=version, extensions=extensions, preamble=preamble, body=body)
             code = GLSL_FRAGMENT_TEMPLATE.format(version=version, extensions=extensions, preamble=preamble, body=body)
-            shader = core.Shader.make(core.Shader.SL_GLSL, vertex_code, code)
+            shader = core.Shader.make(core.Shader.SL_GLSL, vertex_code, code, options=options)
 
         if not shader:
             pytest.fail("error compiling shader:\n" + code)

+ 34 - 0
tests/display/test_glsl_shader.py

@@ -21,6 +21,40 @@ def test_glsl_test_fail(env):
         env.run_glsl("assert(false);")
 
 
+def test_glsl_preproc_error(env):
+    "Make sure that preprocessor errors result in failed compilation."
+
+    preamble = """
+    #error THIS IS EXPECTED TO ERROR
+    """
+
+    with pytest.raises(Failed):
+        env.run_glsl("assert(true);", preamble)
+
+
+def test_glsl_defines(env):
+    preamble = """
+    #ifdef TEST_UNDEFINED
+    #error TEST_UNDEFINED is defined
+    #endif
+
+    #if TEST_DEFINED != 1
+    #error TEST_DEFINED != 1
+    #endif
+
+    #if TEST_DEFINED_2 != 2
+    #error TEST_DEFINED_2 != 2
+    #endif
+    """
+    options = core.CompilerOptions()
+    options.define('TEST_DEFINED')
+    options.define('TEST_DEFINED_2', '2')
+    options.define('TEST_UNDEFINED')
+    options.undef('TEST_UNDEFINED')
+
+    env.run_glsl("assert(true);", preamble, options=options)
+
+
 def test_glsl_sampler(env):
     tex1 = core.Texture("tex1-ubyte-rgba8")
     tex1.setup_1d_texture(1, core.Texture.T_unsigned_byte, core.Texture.F_rgba8)

+ 31 - 0
tests/display/test_glsl_types.py

@@ -0,0 +1,31 @@
+from panda3d.core import Shader, ShaderType, CompilerOptions
+
+
+def compile_parameter(code):
+    options = CompilerOptions()
+    options.optimize = CompilerOptions.Optimize.NONE
+
+    shader = Shader.make_compute(Shader.SL_GLSL, "#version 430\n" + code + "\nvoid main() {}\n", options)
+    module = shader.get_module(Shader.Stage.COMPUTE)
+    return module.parameters[0]
+
+
+def test_glsl_reflect_ssbo():
+    param = compile_parameter("""
+    struct Thing {
+      vec3 pos;
+      uint idx;
+    };
+
+    layout(std430) buffer dataBuffer {
+      Thing things[64];
+    };
+    """)
+
+    assert isinstance(param.type, ShaderType.StorageBuffer)
+
+    # An SSBO is an opaque reference, does not occupy a size
+    assert param.type.size_bytes == 0
+
+    # The contained type does have a size, following std430 rules
+    assert param.type.contained_type.size_bytes == 16 * 64