Selaa lähdekoodia

shaderpipeline: More work: caps, refactoring, legacy, etc. etc.

rdb 2 vuotta sitten
vanhempi
sitoutus
f5e5ff19af

+ 1 - 1
panda/src/display/graphicsStateGuardian.I

@@ -740,7 +740,7 @@ set_shader_model(ShaderModel shader_model) {
 /**
  * Returns a bitmask of the supported shader capabilities.
  */
-INLINE int GraphicsStateGuardian::
+INLINE uint64_t GraphicsStateGuardian::
 get_supported_shader_capabilities() const {
   return _supported_shader_caps;
 }

+ 2 - 2
panda/src/display/graphicsStateGuardian.h

@@ -226,7 +226,7 @@ PUBLISHED:
   INLINE void set_shader_model(ShaderModel shader_model);
   MAKE_PROPERTY(shader_model, get_shader_model, set_shader_model);
 
-  INLINE int get_supported_shader_capabilities() const;
+  INLINE uint64_t get_supported_shader_capabilities() const;
   MAKE_PROPERTY(supported_shader_capabilities, get_supported_shader_capabilities);
 
   virtual int get_supported_geom_rendering() const;
@@ -638,7 +638,7 @@ protected:
 
   ShaderModel _auto_detect_shader_model;
   ShaderModel _shader_model;
-  int _supported_shader_caps;
+  uint64_t _supported_shader_caps;
 
   static PT(TextureStage) _alpha_scale_texture_stage;
 

+ 9 - 5
panda/src/dxgsg9/dxGraphicsStateGuardian9.cxx

@@ -2360,15 +2360,19 @@ reset() {
 
   _supported_shader_caps = 0;
   if (_supports_hlsl) {
-    _supported_shader_caps = ShaderModule::C_basic_shader
-                           | ShaderModule::C_vertex_texture
-                           | ShaderModule::C_sampler_shadow
-                           | ShaderModule::C_non_square_matrices
-                           | ShaderModule::C_texture_lod;
+    _supported_shader_caps = Shader::C_basic_shader
+                           | Shader::C_standard_derivatives
+                           | Shader::C_shadow_samplers
+                           | Shader::C_non_square_matrices
+                           | Shader::C_texture_lod;
 
     _supports_geometry_instancing = true;
   }
 
+  if (d3d_caps.VertexTextureFilterCaps != 0) {
+    _supported_shader_caps |= Shader::C_vertex_texture;
+  }
+
   _vertex_shader_profile = (char *) D3DXGetVertexShaderProfile (_d3d_device);
   _pixel_shader_profile = (char *) D3DXGetPixelShaderProfile (_d3d_device);
 

+ 204 - 98
panda/src/glstuff/glGraphicsStateGuardian_src.cxx

@@ -1115,17 +1115,25 @@ reset() {
 #ifndef OPENGLES
   _supports_cube_map_array = is_at_least_gl_version(4, 0) ||
                              has_extension("GL_ARB_texture_cube_map_array");
+#elif defined(OPENGLES_2)
+  _supports_cube_map_array = is_at_least_gles_version(3, 2) ||
+                             has_extension("GL_OES_texture_cube_map_array") ||
+                             has_extension("GL_EXT_texture_cube_map_array");
 #endif
 
 #ifndef OPENGLES
   if (is_at_least_gl_version(3, 1)) {
     _glTexBuffer = (PFNGLTEXBUFFERPROC)get_extension_func("glTexBuffer");
     _supports_buffer_texture = true;
-
-  } else if (has_extension("GL_ARB_texture_buffer_object")) {
+  }
+  else if (has_extension("GL_ARB_texture_buffer_object")) {
     _glTexBuffer = (PFNGLTEXBUFFERPROC)get_extension_func("glTexBufferARB");
     _supports_buffer_texture = true;
   }
+  else if (has_extension("GL_EXT_texture_buffer_object")) {
+    _glTexBuffer = (PFNGLTEXBUFFERPROC)get_extension_func("glTexBufferEXT");
+    _supports_buffer_texture = true;
+  }
 #endif
 
 #ifdef OPENGLES
@@ -1712,66 +1720,95 @@ reset() {
 #elif defined(OPENGLES)
   _supports_glsl = true;
   _supported_shader_caps =
-      ShaderModule::C_basic_shader |
-      ShaderModule::C_vertex_texture |
-      ShaderModule::C_invariant;
+      Shader::C_basic_shader |
+      Shader::C_vertex_texture |
+      Shader::C_point_coord;
 
   if (is_at_least_gles_version(3, 0)) {
     _supported_shader_caps |=
-      ShaderModule::C_sampler_shadow |
-      ShaderModule::C_non_square_matrices |
-      ShaderModule::C_unsigned_int |
-      ShaderModule::C_flat_interpolation |
-      ShaderModule::C_texture_lod |
-      ShaderModule::C_texture_fetch |
-      ShaderModule::C_int_samplers |
-      ShaderModule::C_sampler_cube_shadow |
-      ShaderModule::C_vertex_id |
-      ShaderModule::C_round_even |
-      ShaderModule::C_instance_id |
-      ShaderModule::C_bit_encoding;
+      Shader::C_standard_derivatives |
+      Shader::C_shadow_samplers |
+      Shader::C_non_square_matrices |
+      Shader::C_unified_model |
+      Shader::C_texture_array |
+      Shader::C_texture_integer |
+      Shader::C_texture_lod |
+      Shader::C_texture_query_size |
+      Shader::C_sampler_cube_shadow |
+      Shader::C_vertex_id |
+      Shader::C_draw_buffers |
+      Shader::C_instance_id |
+      Shader::C_bit_encoding;
+
+    if (has_extension("GL_EXT_texture_query_lod")) {
+      _supported_shader_caps |= Shader::C_texture_query_lod;
+    }
+    if (has_extension("GL_OES_sample_variables")) {
+      _supported_shader_caps |= Shader::C_sample_variables;
+    }
+    if (has_extension("OES_shader_multisample_interpolation")) {
+      _supported_shader_caps |= Shader::C_multisample_interpolation;
+    }
+    if (has_extension("OES_shader_image_atomic")) {
+      _supported_shader_caps |= Shader::C_image_atomic;
+    }
   }
   else {
     if (has_extension("GL_EXT_shadow_samplers")) {
-      _supported_shader_caps |= ShaderModule::C_sampler_shadow;
+      _supported_shader_caps |= Shader::C_shadow_samplers;
+    }
+    if (has_extension("GL_NV_non_square_matrices")) {
+      _supported_shader_caps |= Shader::C_non_square_matrices;
+    }
+    if (has_extension("GL_NV_shadow_samplers_cube")) {
+      _supported_shader_caps |= Shader::C_sampler_cube_shadow;
     }
     if (has_extension("GL_EXT_shader_texture_lod")) {
-      _supported_shader_caps |= ShaderModule::C_texture_lod;
+      _supported_shader_caps |= Shader::C_texture_lod;
+    }
+    if (has_extension("GL_EXT_draw_buffers")) {
+      _supported_shader_caps |= Shader::C_draw_buffers;
     }
   }
 
   if (is_at_least_gles_version(3, 1)) {
     _supported_shader_caps |=
-      ShaderModule::C_texture_gather |
-      ShaderModule::C_extended_arithmetic |
-      ShaderModule::C_image_load_store |
-      ShaderModule::C_compute_shader;
+      Shader::C_texture_gather_red |
+      Shader::C_texture_gather_any |
+      Shader::C_extended_arithmetic |
+      Shader::C_atomic_counters |
+      Shader::C_image_load_store |
+      Shader::C_image_query_size |
+      Shader::C_compute_shader;
+
+    if (has_extension("GL_OES_geometry_shader")) {
+      _supported_shader_caps |=
+        Shader::C_geometry_shader |
+        Shader::C_primitive_id;
+    }
+    if (has_extension("GL_OES_texture_buffer")) {
+      _supported_shader_caps |= Shader::C_texture_buffer;
+    }
+    if (_supports_cube_map_array) {
+      _supported_shader_caps |= Shader::C_cube_map_array;
+    }
   }
 
   if (is_at_least_gles_version(3, 2)) {
     _supported_shader_caps |=
-      ShaderModule::C_double |
-      ShaderModule::C_cube_map_array |
-      ShaderModule::C_tessellation_shader |
-      ShaderModule::C_sample_variables |
-      ShaderModule::C_buffer_texture;
-
-    if (has_extension("GL_EXT_geometry_shader")) {
-      _supported_shader_caps |=
-        ShaderModule::C_geometry_shader |
-        ShaderModule::C_primitive_id;
-    }
-    if (has_extension("GL_EXT_tessellation_shader")) {
-      _supported_shader_caps |= ShaderModule::C_tessellation_shader;
-    }
+      Shader::C_texture_buffer |
+      Shader::C_geometry_shader |
+      Shader::C_primitive_id |
+      Shader::C_cube_map_array |
+      Shader::C_geometry_shader_instancing |
+      Shader::C_tessellation_shader |
+      Shader::C_sample_variables |
+      Shader::C_multisample_interpolation |
+      Shader::C_image_atomic;
   }
-  else {
-    if (has_extension("GL_OES_sample_variables")) {
-      _supported_shader_caps |= ShaderModule::C_sample_variables;
-    }
-    if (has_extension("GL_OES_texture_buffer")) {
-      _supported_shader_caps |= ShaderModule::C_buffer_texture;
-    }
+
+  if (has_extension("GL_EXT_clip_cull_distance")) {
+    _supported_shader_caps |= Shader::C_clip_distance;
   }
 
 #else
@@ -1789,115 +1826,184 @@ reset() {
     // 4.1 guarantees support for 4.10
     // 4.x guarantees support for 1.40-4.x0 (for x >= 2)
     _supported_shader_caps |=
-      ShaderModule::C_basic_shader |
-      ShaderModule::C_vertex_texture |
-      ShaderModule::C_sampler_shadow;
+      Shader::C_basic_shader |
+      Shader::C_point_coord |
+      Shader::C_standard_derivatives |
+      Shader::C_shadow_samplers;
 
     // OpenGL 2.1
     if (_glsl_version >= 120) {
-      _supported_shader_caps |=
-        ShaderModule::C_invariant |
-        ShaderModule::C_non_square_matrices;
+      _supported_shader_caps |= Shader::C_non_square_matrices;
     }
 
     // OpenGL 3.0
     if (_glsl_version >= 130) {
       _supported_shader_caps |=
-        ShaderModule::C_unsigned_int |
-        ShaderModule::C_flat_interpolation |
-        ShaderModule::C_noperspective_interpolation |
-        ShaderModule::C_texture_lod |
-        ShaderModule::C_texture_fetch |
-        ShaderModule::C_int_samplers |
-        ShaderModule::C_sampler_cube_shadow |
-        ShaderModule::C_vertex_id |
-        ShaderModule::C_round_even;
+        Shader::C_vertex_texture |
+        Shader::C_texture_lod |
+        Shader::C_unified_model |
+        Shader::C_noperspective_interpolation |
+        Shader::C_texture_array |
+        Shader::C_texture_integer |
+        Shader::C_texture_query_size |
+        Shader::C_sampler_cube_shadow |
+        Shader::C_vertex_id |
+        Shader::C_draw_buffers |
+        Shader::C_clip_distance;
     }
     else {
+      GLint max_vertex_texture_units = 0;
+      glGetIntegerv(GL_MAX_VERTEX_TEXTURE_IMAGE_UNITS, &max_vertex_texture_units);
+      if (max_vertex_texture_units > 0) {
+        _supported_shader_caps |= Shader::C_vertex_texture;
+      }
       if (has_extension("GL_ARB_shader_texture_lod")) {
-        _supported_shader_caps |= ShaderModule::C_texture_lod;
+        _supported_shader_caps |= Shader::C_texture_lod;
       }
       if (has_extension("GL_EXT_gpu_shader4")) {
         // These are in principle supported, but SPIRV-Cross might need to be
         // changed to be able to leverage these.  Most cards that have this
         // extension also support GLSL 1.30, so it might not be worth adding.
         _supported_shader_caps |=
-          //ShaderModule::C_unsigned_int |
-          ShaderModule::C_texture_fetch |
-          //ShaderModule::C_sampler_cube_shadow |
-          ShaderModule::C_round_even;
+          Shader::C_unified_model |
+          Shader::C_noperspective_interpolation |
+          Shader::C_texture_lod |
+          Shader::C_texture_query_size |
+          Shader::C_sampler_cube_shadow |
+          Shader::C_instance_id | // zero if instancing not supported
+          Shader::C_primitive_id;
+
+        if (has_extension("GL_EXT_texture_integer")) {
+          _supported_shader_caps |= Shader::C_texture_integer;
+        }
+        if (has_extension("GL_EXT_texture_array")) {
+          _supported_shader_caps |= Shader::C_texture_array;
+        }
+        if (has_extension("GL_EXT_texture_buffer_object")) {
+          _supported_shader_caps |= Shader::C_texture_buffer;
+        }
+      }
+      if (is_at_least_gl_version(2, 0) || has_extension("GL_ARB_draw_buffers")) {
+        _supported_shader_caps |= Shader::C_draw_buffers;
       }
     }
 
     // OpenGL 3.1
     if (_glsl_version >= 140 || has_extension("GL_ARB_draw_instanced")) {
-      _supported_shader_caps |= ShaderModule::C_instance_id;
+      _supported_shader_caps |= Shader::C_instance_id;
     }
     if (_glsl_version >= 140 || has_extension("GL_ARB_texture_buffer_object")) {
-      _supported_shader_caps |= ShaderModule::C_buffer_texture;
+      _supported_shader_caps |= Shader::C_texture_buffer;
     }
 
     // OpenGL 3.2
     if (_glsl_version >= 150 || has_extension("GL_ARB_geometry_shader4")) {
       _supported_shader_caps |=
-        ShaderModule::C_geometry_shader |
-        ShaderModule::C_primitive_id;
+        Shader::C_geometry_shader |
+        Shader::C_primitive_id;
       _supported_geom_rendering |= Geom::GR_adjacency;
     }
 
     if (_glsl_version >= 330 || has_extension("GL_ARB_shader_bit_encoding")) {
-      _supported_shader_caps |= ShaderModule::C_bit_encoding;
+      _supported_shader_caps |= Shader::C_bit_encoding;
     }
 
     if (_glsl_version >= 400) {
       _supported_shader_caps |=
-        ShaderModule::C_texture_gather |
-        ShaderModule::C_double |
-        ShaderModule::C_cube_map_array |
-        ShaderModule::C_tessellation_shader |
-        ShaderModule::C_sample_variables |
-        ShaderModule::C_extended_arithmetic |
-        ShaderModule::C_texture_query_lod;
+        Shader::C_texture_query_lod |
+        Shader::C_texture_gather_red |
+        Shader::C_texture_gather_any |
+        Shader::C_double |
+        Shader::C_cube_map_array |
+        Shader::C_geometry_shader_instancing |
+        Shader::C_tessellation_shader |
+        Shader::C_sample_variables |
+        Shader::C_extended_arithmetic |
+        Shader::C_multisample_interpolation;
     }
     else {
-      // This extension is very limited, should we break up C_texture_gather?
-      //if (has_extension("GL_ARB_texture_gather")) {
-      //  _supported_shader_caps |= ShaderModule::C_texture_gather;
-      //}
+      if (has_extension("GL_ARB_texture_query_lod")) {
+        _supported_shader_caps |= Shader::C_texture_query_lod;
+      }
+      if (has_extension("GL_ARB_texture_gather")) {
+        _supported_shader_caps |= Shader::C_texture_gather_red;
+      }
       if (has_extension("GL_ARB_gpu_shader_fp64")) {
-        _supported_shader_caps |= ShaderModule::C_double;
+        _supported_shader_caps |= Shader::C_double;
+      }
+      if (has_extension("GL_ARB_gpu_shader5")) {
+        _supported_shader_caps |=
+          Shader::C_texture_gather_red |
+          Shader::C_texture_gather_any |
+          Shader::C_geometry_shader_instancing;
       }
       if (has_extension("GL_ARB_tessellation_shader")) {
-        _supported_shader_caps |= ShaderModule::C_tessellation_shader;
+        _supported_shader_caps |= Shader::C_tessellation_shader;
       }
-      if (has_extension("GL_ARB_texture_query_lod")) {
-        _supported_shader_caps |= ShaderModule::C_texture_query_lod;
+      if (has_extension("GL_ARB_sample_shading")) {
+        _supported_shader_caps |= Shader::C_sample_variables;
       }
     }
 
-    if (_glsl_version >= 420 || has_extension("GL_ARB_shader_image_load_store")) {
-      _supported_shader_caps |= ShaderModule::C_image_load_store;
+    if (_glsl_version >= 420) {
+      _supported_shader_caps |=
+        Shader::C_atomic_counters |
+        Shader::C_image_load_store |
+        Shader::C_image_atomic;
     }
-
-    if (_glsl_version >= 430 || has_extension("GL_ARB_compute_shader")) {
-      _supported_shader_caps |= ShaderModule::C_compute_shader;
+    else {
+      if (has_extension("GL_ARB_shader_atomic_counters")) {
+        _supported_shader_caps |= Shader::C_atomic_counters;
+      }
+      if (has_extension("GL_ARB_shader_image_load_store")) {
+        _supported_shader_caps |=
+          Shader::C_image_load_store |
+          Shader::C_image_atomic;
+      }
     }
 
-    if (_glsl_version >= 430 || has_extension("GL_ARB_texture_query_levels")) {
-      _supported_shader_caps |= ShaderModule::C_texture_query_levels;
+    if (_glsl_version >= 430) {
+      _supported_shader_caps |=
+        Shader::C_image_query_size |
+        Shader::C_texture_query_levels |
+        Shader::C_storage_buffer |
+        Shader::C_compute_shader;
+    }
+    else {
+      if (has_extension("GL_ARB_shader_image_size")) {
+        _supported_shader_caps |= Shader::C_image_query_size;
+      }
+      if (has_extension("GL_ARB_texture_query_levels")) {
+        _supported_shader_caps |= Shader::C_texture_query_levels;
+      }
+      if (has_extension("GL_ARB_shader_storage_buffer_object")) {
+        _supported_shader_caps |= Shader::C_storage_buffer;
+      }
+      if (has_extension("GL_ARB_compute_shader")) {
+        _supported_shader_caps |= Shader::C_compute_shader;
+      }
     }
 
     if (_glsl_version >= 440 || has_extension("GL_ARB_enhanced_layouts")) {
-      _supported_shader_caps |= ShaderModule::C_enhanced_layouts;
+      _supported_shader_caps |= Shader::C_enhanced_layouts;
     }
 
     if (_glsl_version >= 450) {
       _supported_shader_caps |=
-        ShaderModule::C_derivative_control |
-        ShaderModule::C_texture_query_samples;
+        Shader::C_derivative_control |
+        Shader::C_texture_query_samples |
+        Shader::C_cull_distance;
     }
-    else if (has_extension("GL_ARB_derivative_control")) {
-      _supported_shader_caps |= ShaderModule::C_derivative_control;
+    else {
+      if (has_extension("GL_ARB_derivative_control")) {
+        _supported_shader_caps |= Shader::C_derivative_control;
+      }
+      if (has_extension("GL_ARB_shader_texture_image_samples")) {
+        _supported_shader_caps |= Shader::C_texture_query_samples;
+      }
+      if (has_extension("GL_ARB_cull_distance")) {
+        _supported_shader_caps |= Shader::C_cull_distance;
+      }
     }
   }
 
@@ -2055,7 +2161,7 @@ reset() {
          get_extension_func("glUniformMatrix4x3fv");
 
       if (_glUniformMatrix3x4fv == nullptr || _glUniformMatrix4x3fv == nullptr) {
-        _supported_shader_caps &= ~ShaderModule::C_non_square_matrices;
+        _supported_shader_caps &= ~Shader::C_non_square_matrices;
         GLCAT.warning()
           << "GLSL 1.20 advertised as supported by OpenGL runtime, but could "
              "not get pointers to functions to set non-square matrices.\n";
@@ -3809,7 +3915,7 @@ reset() {
   if (GLCAT.is_debug()) {
     std::ostream &out = GLCAT.debug();
     out << "shader caps = ";
-    ShaderModule::output_capabilities(out, _supported_shader_caps);
+    Shader::output_capabilities(out, _supported_shader_caps);
     out << "\n";
     GLCAT.debug() << "shader model = " << _shader_model << "\n";
   }

+ 23 - 7
panda/src/glstuff/glShaderContext_src.cxx

@@ -3569,7 +3569,10 @@ attach_shader(const ShaderModule *module, Shader::ModuleSpecConstants &consts) {
 #ifdef OPENGLES
       options.es = true;
 #else
-      options.es = false;
+      options.es = _glgsg->_glsl_version == 100
+                || _glgsg->_glsl_version == 300
+                || _glgsg->_glsl_version == 310
+                || _glgsg->_glsl_version == 320;
 #endif
       options.vertex.support_nonzero_base_instance = false;
       options.enable_420pack_extension = false;
@@ -3579,15 +3582,28 @@ attach_shader(const ShaderModule *module, Shader::ModuleSpecConstants &consts) {
         _emulate_float_attribs = true;
       }
 
-      // At this time, SPIRV-Cross doesn't add this extension automatically.
-      if (!options.es && options.version < 140 &&
-          (module->get_used_capabilities() & ShaderModule::C_instance_id) != 0) {
-        if (_glgsg->has_extension("GL_ARB_draw_instanced")) {
-          compiler.require_extension("GL_ARB_draw_instanced");
-        } else {
+      // At this time, SPIRV-Cross doesn't always add these automatically.
+      uint64_t used_caps = module->get_used_capabilities();
+#ifndef OPENGLES
+      if (!options.es) {
+        if (options.version < 140 && (used_caps & Shader::C_instance_id) != 0) {
+          if (_glgsg->has_extension("GL_ARB_draw_instanced")) {
+            compiler.require_extension("GL_ARB_draw_instanced");
+          } else {
+            compiler.require_extension("GL_EXT_gpu_shader4");
+          }
+        }
+        if (options.version < 130 && (used_caps & Shader::C_unified_model) != 0) {
           compiler.require_extension("GL_EXT_gpu_shader4");
         }
       }
+      else
+#endif
+      {
+        if (options.version < 300 && (used_caps & Shader::C_non_square_matrices) != 0) {
+          compiler.require_extension("GL_NV_non_square_matrices");
+        }
+      }
 
       // Assign names based on locations.  This is important to make sure that
       // uniforms shared between shader stages have the same name, or the

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

@@ -8,6 +8,7 @@
 #include "shader.cxx"
 #include "shaderBuffer.cxx"
 #include "shaderContext.cxx"
+#include "shaderEnums.cxx"
 #include "shaderModule.cxx"
 #include "shaderType.cxx"
 #include "simpleAllocator.cxx"

+ 17 - 9
panda/src/gobj/shader.cxx

@@ -3117,18 +3117,26 @@ add_module(PT(ShaderModule) module) {
 
   int used_caps = module->get_used_capabilities();
 
-  // Make sure we have a unique copy.
+  // Make sure that any modifications made to the module will not affect other
+  // Shader objects, by storing it as copy-on-write.
   COWPT(ShaderModule) cow_module = std::move(module);
 
-  // Link its inputs up with the previous stage.
   if (!_modules.empty()) {
-    //TODO: Check whether it's already linked properly to possibly avoid cloning
-    // the module.
-    PT(ShaderModule) module = cow_module.get_write_pointer();
-    if (!module->link_inputs(_modules.back()._module.get_read_pointer())) {
-      shader_cat.error()
-        << "Unable to match shader module interfaces.\n";
-      return false;
+    // Link its inputs up with the previous stage.
+    pmap<int, int> location_remap;
+    {
+      CPT(ShaderModule) module = cow_module.get_read_pointer();
+      if (!module->link_inputs(_modules.back()._module.get_read_pointer(), location_remap)) {
+        shader_cat.error()
+          << "Unable to match shader module interfaces.\n";
+        return false;
+      }
+    }
+
+    if (!location_remap.empty()) {
+      // Make sure we have a unique copy so that we can do the remappings.
+      PT(ShaderModule) module = cow_module.get_write_pointer();
+      module->remap_input_locations(location_remap);
     }
   }
   else if (stage == Stage::vertex) {

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

@@ -45,7 +45,7 @@ class ShaderCompiler;
 /**
 
  */
-class EXPCL_PANDA_GOBJ Shader : public TypedWritableReferenceCount {
+class EXPCL_PANDA_GOBJ Shader : public TypedWritableReferenceCount, public ShaderEnums {
 PUBLISHED:
   using Stage = ShaderModule::Stage;
   using ScalarType = ShaderType::ScalarType;
@@ -85,10 +85,9 @@ PUBLISHED:
     bit_AutoShaderShadow = 4, // bit for AS_shadow
   };
 
-private:
+PUBLISHED:
   Shader(ShaderLanguage lang);
 
-PUBLISHED:
   static PT(Shader) load(const Filename &file, ShaderLanguage lang = SL_none);
   static PT(Shader) make(std::string body, ShaderLanguage lang = SL_none);
   static PT(Shader) load(ShaderLanguage lang,

+ 167 - 0
panda/src/gobj/shaderEnums.cxx

@@ -0,0 +1,167 @@
+/**
+ * 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 shaderEnums.cxx
+ * @author rdb
+ * @date 2023-01-22
+ */
+
+#include "shaderEnums.h"
+
+/**
+ * Returns the stage as a string.
+ */
+std::string ShaderEnums::
+format_stage(Stage stage) {
+  switch (stage) {
+  case Stage::vertex:
+    return "vertex";
+  case Stage::tess_control:
+    return "tess_control";
+  case Stage::tess_evaluation:
+    return "tess_evaluation";
+  case Stage::geometry:
+    return "geometry";
+  case Stage::fragment:
+    return "fragment";
+  case Stage::compute:
+    return "compute";
+  }
+
+  return "**invalid**";
+}
+
+/**
+ * Outputs the given capabilities mask.
+ */
+void ShaderEnums::
+output_capabilities(std::ostream &out, int caps) {
+  if (caps & C_basic_shader) {
+    out << "basic_shader ";
+  }
+  if (caps & C_vertex_texture) {
+    out << "vertex_texture ";
+  }
+  if (caps & C_point_coord) {
+    out << "point_coord ";
+  }
+  if (caps & C_standard_derivatives) {
+    out << "standard_derivatives ";
+  }
+  if (caps & C_shadow_samplers) {
+    out << "shadow_samplers ";
+  }
+  if (caps & C_non_square_matrices) {
+    out << "non_square_matrices ";
+  }
+  if (caps & C_texture_lod) {
+    out << "texture_lod ";
+  }
+  if (caps & C_unified_model) {
+    out << "unified_model ";
+  }
+  if (caps & C_noperspective_interpolation) {
+    out << "noperspective_interpolation ";
+  }
+  if (caps & C_texture_integer) {
+    out << "texture_integer ";
+  }
+  if (caps & C_texture_query_size) {
+    out << "texture_query_size ";
+  }
+  if (caps & C_sampler_cube_shadow) {
+    out << "sampler_cube_shadow ";
+  }
+  if (caps & C_vertex_id) {
+    out << "vertex_id ";
+  }
+  if (caps & C_draw_buffers) {
+    out << "draw_buffers ";
+  }
+  if (caps & C_clip_distance) {
+    out << "clip_distance ";
+  }
+  if (caps & C_instance_id) {
+    out << "instance_id ";
+  }
+  if (caps & C_texture_buffer) {
+    out << "texture_buffer ";
+  }
+  if (caps & C_geometry_shader) {
+    out << "geometry_shader ";
+  }
+  if (caps & C_primitive_id) {
+    out << "primitive_id ";
+  }
+  if (caps & C_bit_encoding) {
+    out << "bit_encoding ";
+  }
+  if (caps & C_texture_query_lod) {
+    out << "texture_query_lod ";
+  }
+  if (caps & C_texture_gather_red) {
+    out << "texture_gather_red ";
+  }
+  if (caps & C_texture_gather_any) {
+    out << "texture_gather_any ";
+  }
+  if (caps & C_extended_arithmetic) {
+    out << "extended_arithmetic ";
+  }
+  if (caps & C_double) {
+    out << "double ";
+  }
+  if (caps & C_cube_map_array) {
+    out << "cube_map_array ";
+  }
+  if (caps & C_geometry_shader_instancing) {
+    out << "geometry_shader_instancing ";
+  }
+  if (caps & C_tessellation_shader) {
+    out << "tessellation_shader ";
+  }
+  if (caps & C_sample_variables) {
+    out << "sample_variables ";
+  }
+  if (caps & C_multisample_interpolation) {
+    out << "multisample_interpolation ";
+  }
+  if (caps & C_atomic_counters) {
+    out << "atomic_counters ";
+  }
+  if (caps & C_image_load_store) {
+    out << "image_load_store ";
+  }
+  if (caps & C_image_atomic) {
+    out << "image_atomic ";
+  }
+  if (caps & C_image_query_size) {
+    out << "image_query_size ";
+  }
+  if (caps & C_texture_query_levels) {
+    out << "texture_query_levels ";
+  }
+  if (caps & C_storage_buffer) {
+    out << "storage_buffer ";
+  }
+  if (caps & C_compute_shader) {
+    out << "compute_shader ";
+  }
+  if (caps & C_enhanced_layouts) {
+    out << "enhanced_layouts ";
+  }
+  if (caps & C_cull_distance) {
+    out << "cull_distance ";
+  }
+  if (caps & C_derivative_control) {
+    out << "derivative_control ";
+  }
+  if (caps & C_texture_query_samples) {
+    out << "texture_query_samples ";
+  }
+}

+ 139 - 0
panda/src/gobj/shaderEnums.h

@@ -0,0 +1,139 @@
+/**
+ * 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 shaderEnums.h
+ * @author rdb
+ * @date 2023-01-22
+ */
+
+#ifndef SHADERENUMS_H
+#define SHADERENUMS_H
+
+#include "copyOnWriteObject.h"
+#include "bamCacheRecord.h"
+#include "shaderType.h"
+#include "internalName.h"
+
+/**
+ * Provides scoping for various enum types that are used by both ShaderModule
+ * and Shader classes.
+ */
+class EXPCL_PANDA_GOBJ ShaderEnums {
+PUBLISHED:
+  enum class Stage {
+    vertex,
+    tess_control,
+    tess_evaluation,
+    geometry,
+    fragment,
+    compute,
+  };
+
+  /**
+   * Indicates which features are used by the shader, which can be used by the
+   * driver to check whether cross-compilation is possible, or whether certain
+   * transformation steps may need to be applied.
+   */
+  enum Capabilities : uint64_t {
+    // Always set, indicates that we support basic vertex+fragment shaders.
+    C_basic_shader = 1ull << 0,
+
+    C_vertex_texture = 1ull << 1,
+    C_point_coord = 1ull << 2,
+    C_standard_derivatives = 1ull << 3,
+    C_shadow_samplers = 1ull << 4, // 1D and 2D only
+
+    // GLSL 1.20
+    C_non_square_matrices = 1ull << 5,
+
+    // GLSL 1.30 / SM 3.0
+    C_texture_lod = 1ull << 6, // textureLod in vshader doesn't count, textureGrad does
+
+    /**
+     * This cap deserves some explanation: it refers to the new shader model
+     * exposed by hardware capable of OpenGL 3.0 / DirectX 10 (SM 4.0) and above
+     * or GL_EXT_gpu_shader4, adding features like:
+     * - "true" (non-emulated) 32-bit integers and operations
+     * - unsigned integers
+     * - integer samplers (if C_texture_integer is also present)
+     * - integer varyings
+     * - integer bitwise operations
+     * - flat interpolation
+     * - roundEven
+     * - texelFetch
+     * Because all these features require the new shader model and are not
+     * offered individually, they have been wrapped up in a single bit flag.
+     * A shader with this bit set will not work with DirectX 9.
+     */
+    C_unified_model = 1ull << 7,
+
+    C_noperspective_interpolation = 1ull << 8, // not supported in GLES
+    C_texture_array = 1ull << 9,
+    C_texture_integer = 1ull << 10, // usampler2D, isampler2D, etc.
+    C_texture_query_size = 1ull << 11, // textureSize, etc. (could be emulated)
+    C_sampler_cube_shadow = 1ull << 12,
+    C_vertex_id = 1ull << 13,
+    C_draw_buffers = 1ull << 14, // MRT
+    C_clip_distance = 1ull << 15, // gl_ClipDistance
+
+    // GLSL 1.40 / SM 4.0
+    C_instance_id = 1ull << 16, // ES 3.00
+    C_texture_buffer = 1ull << 17, // ES 3.20
+
+    // GLSL 1.50 / ES 3.20 / SM 4.0
+    C_geometry_shader = 1ull << 18,
+    C_primitive_id = 1ull << 19,
+
+    // GLSL 3.30 / ES 3.00 / SM 4.0
+    C_bit_encoding = 1ull << 20,
+
+    // GLSL 4.00 / SM 4.1
+    C_texture_query_lod = 1ull << 21,
+    C_texture_gather_red = 1ull << 22,
+
+    // GLSL 4.00 / ES 3.10 / SM 5.0
+    C_texture_gather_any = 1ull << 23,
+    C_extended_arithmetic = 1ull << 24,
+    C_double = 1ull << 25, // Not in ES
+
+    // GLSL 4.00 / ES 3.20 / SM 5.0
+    C_cube_map_array = 1ull << 26,
+    C_geometry_shader_instancing = 1ull << 27,
+    C_tessellation_shader = 1ull << 28,
+    C_sample_variables = 1ull << 29,
+    C_multisample_interpolation = 1ull << 30,
+
+    // GLSL 4.20 / ES 3.10 / SM 5.0
+    C_atomic_counters = 1ull << 31,
+    C_image_load_store = 1ull << 32,
+    C_image_atomic = 1ull << 33, // ES 3.20 or OES_shader_image_atomic
+
+    // GLSL 4.30 / ES 3.10 / SM 5.0
+    C_image_query_size = 1ull << 34,
+    C_texture_query_levels = 1ull << 35, // not in ES
+    C_storage_buffer = 1ull << 36,
+    C_compute_shader = 1ull << 37,
+
+    // GLSL 4.40 / ARB_enhanced_layouts
+    C_enhanced_layouts = 1ull << 38,
+
+    // GLSL 4.50
+    C_cull_distance = 1ull << 39,
+    C_derivative_control = 1ull << 40,
+    C_texture_query_samples = 1ull << 41,
+  };
+
+  static std::string format_stage(Stage stage);
+  static void output_capabilities(std::ostream &out, int capabilities);
+};
+
+INLINE std::ostream &operator << (std::ostream &out, ShaderEnums::Stage stage) {
+  return out << ShaderEnums::format_stage(stage);
+}
+
+#endif

+ 1 - 1
panda/src/gobj/shaderModule.I

@@ -23,7 +23,7 @@ get_stage() const {
 /**
  * Returns the set of capabilities used by this shader.
  */
-INLINE int ShaderModule::
+INLINE uint64_t ShaderModule::
 get_used_capabilities() const {
   return _used_caps;
 }

+ 14 - 131
panda/src/gobj/shaderModule.cxx

@@ -47,24 +47,33 @@ ShaderModule::
 }
 
 /**
- * Adjusts any input bindings necessary to be able to link up with the given
- * previous stage.  Should return false to indicate that the link is not
- * possible.
+ * Links the stage with the given previous stage, by matching up its inputs with
+ * the outputs of the previous stage.  Rather than reassigning the locations
+ * directly, this method just returns the location remappings that need to be
+ * made, or returns false if the stages cannot be linked.
  */
 bool ShaderModule::
-link_inputs(const ShaderModule *previous) {
+link_inputs(const ShaderModule *previous, pmap<int, int> &remap) const {
   // By default we need to do nothing special to link it up, as long as the
   // modules have the same type.
   return get_stage() > previous->get_stage()
       && get_type() == previous->get_type();
 }
 
+/**
+ * Remaps inputs with a given location to a given other location.  Locations
+ * not included in the map remain untouched.
+ */
+void ShaderModule::
+remap_input_locations(const pmap<int, int> &locations) {
+}
+
 /**
  * Remaps parameters with a given location to a given other location.  Locations
  * not included in the map remain untouched.
  */
 void ShaderModule::
-remap_parameter_locations(pmap<int, int> &locations) {
+remap_parameter_locations(const pmap<int, int> &locations) {
 }
 
 /**
@@ -96,129 +105,3 @@ fillin(DatagramIterator &scan, BamReader *manager) {
   _source_filename = scan.get_string();
   _used_caps = (int)scan.get_uint64();
 }
-
-/**
- * Returns the stage as a string.
- */
-std::string ShaderModule::
-format_stage(Stage stage) {
-  switch (stage) {
-  case Stage::vertex:
-    return "vertex";
-  case Stage::tess_control:
-    return "tess_control";
-  case Stage::tess_evaluation:
-    return "tess_evaluation";
-  case Stage::geometry:
-    return "geometry";
-  case Stage::fragment:
-    return "fragment";
-  case Stage::compute:
-    return "compute";
-  }
-
-  return "**invalid**";
-}
-
-/**
- * Outputs the given capabilities mask.
- */
-void ShaderModule::
-output_capabilities(std::ostream &out, int caps) {
-  if (caps & C_basic_shader) {
-    out << "basic_shader ";
-  }
-  if (caps & C_vertex_texture) {
-    out << "vertex_texture ";
-  }
-  if (caps & C_sampler_shadow) {
-    out << "sampler_shadow ";
-  }
-  if (caps & C_invariant) {
-    out << "invariant ";
-  }
-  if (caps & C_non_square_matrices) {
-    out << "non_square_matrices ";
-  }
-  if (caps & C_unsigned_int) {
-    out << "unsigned_int ";
-  }
-  if (caps & C_flat_interpolation) {
-    out << "flat_interpolation ";
-  }
-  if (caps & C_noperspective_interpolation) {
-    out << "noperspective_interpolation ";
-  }
-  if (caps & C_texture_lod) {
-    out << "texture_lod ";
-  }
-  if (caps & C_texture_fetch) {
-    out << "texture_fetch ";
-  }
-  if (caps & C_int_samplers) {
-    out << "int_samplers ";
-  }
-  if (caps & C_sampler_cube_shadow) {
-    out << "sampler_cube_shadow ";
-  }
-  if (caps & C_vertex_id) {
-    out << "vertex_id ";
-  }
-  if (caps & C_round_even) {
-    out << "round_even ";
-  }
-  if (caps & C_instance_id) {
-    out << "instance_id ";
-  }
-  if (caps & C_buffer_texture) {
-    out << "buffer_texture ";
-  }
-  if (caps & C_geometry_shader) {
-    out << "geometry_shader ";
-  }
-  if (caps & C_primitive_id) {
-    out << "primitive_id ";
-  }
-  if (caps & C_bit_encoding) {
-    out << "bit_encoding ";
-  }
-  if (caps & C_texture_gather) {
-    out << "texture_gather ";
-  }
-  if (caps & C_double) {
-    out << "double ";
-  }
-  if (caps & C_cube_map_array) {
-    out << "cube_map_array ";
-  }
-  if (caps & C_tessellation_shader) {
-    out << "tessellation_shader ";
-  }
-  if (caps & C_sample_variables) {
-    out << "sample_variables ";
-  }
-  if (caps & C_extended_arithmetic) {
-    out << "extended_arithmetic ";
-  }
-  if (caps & C_texture_query_lod) {
-    out << "texture_query_lod ";
-  }
-  if (caps & C_image_load_store) {
-    out << "image_load_store ";
-  }
-  if (caps & C_compute_shader) {
-    out << "compute_shader ";
-  }
-  if (caps & C_texture_query_levels) {
-    out << "texture_query_levels ";
-  }
-  if (caps & C_enhanced_layouts) {
-    out << "enhanced_layouts ";
-  }
-  if (caps & C_derivative_control) {
-    out << "derivative_control ";
-  }
-  if (caps & C_texture_query_samples) {
-    out << "texture_query_samples ";
-  }
-}

+ 10 - 84
panda/src/gobj/shaderModule.h

@@ -14,10 +14,11 @@
 #ifndef SHADERMODULE_H
 #define SHADERMODULE_H
 
-#include "copyOnWriteObject.h"
 #include "bamCacheRecord.h"
-#include "shaderType.h"
+#include "copyOnWriteObject.h"
 #include "internalName.h"
+#include "shaderEnums.h"
+#include "shaderType.h"
 
 /**
  * Represents a single shader module in some intermediate representation for
@@ -28,17 +29,8 @@
  * shared between multiple Shader objects, with a unique copy automatically
  * being created if the Shader needs to manipulate the module.
  */
-class EXPCL_PANDA_GOBJ ShaderModule : public CopyOnWriteObject {
+class EXPCL_PANDA_GOBJ ShaderModule : public CopyOnWriteObject, public ShaderEnums {
 PUBLISHED:
-  enum class Stage {
-    vertex,
-    tess_control,
-    tess_evaluation,
-    geometry,
-    fragment,
-    compute,
-  };
-
   /**
    * Defines an interface variable.
    */
@@ -72,7 +64,7 @@ public:
   virtual ~ShaderModule();
 
   INLINE Stage get_stage() const;
-  INLINE int get_used_capabilities() const;
+  INLINE uint64_t get_used_capabilities() const;
 
   INLINE const Filename &get_source_filename() const;
   INLINE void set_source_filename(const Filename &);
@@ -94,11 +86,13 @@ public:
 
   typedef pmap<CPT_InternalName, Variable *> VariablesByName;
 
-  virtual bool link_inputs(const ShaderModule *previous);
-  virtual void remap_parameter_locations(pmap<int, int> &remap);
+  virtual bool link_inputs(const ShaderModule *previous, pmap<int, int> &remap) const;
+  virtual void remap_input_locations(const pmap<int, int> &remap);
+  virtual void remap_parameter_locations(const pmap<int, int> &remap);
 
 PUBLISHED:
   MAKE_PROPERTY(stage, get_stage);
+  MAKE_PROPERTY(used_capabilities, get_used_capabilities);
   MAKE_SEQ_PROPERTY(inputs, get_num_inputs, get_input);
   MAKE_SEQ_PROPERTY(outputs, get_num_outputs, get_output);
   MAKE_SEQ_PROPERTY(spec_constants, get_num_spec_constants, get_spec_constant);
@@ -106,70 +100,6 @@ PUBLISHED:
   virtual std::string get_ir() const=0;
 
 public:
-  /**
-   * Indicates which features are used by the shader, which can be used by the
-   * driver to check whether cross-compilation is possible, or whether certain
-   * transformation steps may need to be applied.
-   */
-  enum Capabilities : uint64_t {
-    C_basic_shader = 1ull << 0,
-    C_vertex_texture = 1ull << 1,
-    C_sampler_shadow = 1ull << 2, // 1D and 2D only
-    C_point_sprites = 1ull << 3,
-
-    // GLSL 1.20
-    C_invariant = 1ull << 4,
-    C_non_square_matrices = 1ull << 5,
-
-    // GLSL 1.30
-    C_unsigned_int = 1ull << 6,
-    C_flat_interpolation = 1ull << 7, // also necessary for int varyings
-    C_noperspective_interpolation = 1ull << 8, // not supported in GLES
-    C_texture_lod = 1ull << 9, // textureLod in vshader doesn't count, textureGrad does
-    C_texture_fetch = 1ull << 10, // texelFetch, textureSize, etc.
-    C_int_samplers = 1ull << 11, // usampler2D, isampler2D, etc.
-    C_sampler_cube_shadow = 1ull << 12,
-    C_vertex_id = 1ull << 13,
-    C_round_even = 1ull << 14, // roundEven function, also in SM 4.0
-
-    // GLSL 1.40 / SM 4.0
-    C_instance_id = 1ull << 15,
-    C_buffer_texture = 1ull << 16, // ES 3.20
-
-    // GLSL 1.50 / SM 4.0
-    C_geometry_shader = 1ull << 17,
-    C_primitive_id = 1ull << 18,
-
-    // GLSL 3.30 / ES 3.00 / SM 4.0
-    C_bit_encoding = 1ull << 19,
-
-    // GLSL 4.00 / ES 3.20 / SM 5.0
-    C_texture_gather = 1ull << 20,
-    C_double = 1ull << 21,
-    C_cube_map_array = 1ull << 22,
-    C_tessellation_shader = 1ull << 23,
-    C_sample_variables = 1ull << 24,
-    C_extended_arithmetic = 1ull << 25,
-    C_texture_query_lod = 1ull << 26, // not in ES
-
-    // GLSL 4.20 / ES 3.10 / SM 5.0
-    C_image_load_store = 1ull << 27,
-
-    // GLSL 4.30 / ES 3.10 / SM 5.0
-    C_compute_shader = 1ull << 28,
-    C_texture_query_levels = 1ull << 29, // not in ES
-
-    // GLSL 4.40 / ARB_enhanced_layouts
-    C_enhanced_layouts = 1ull << 30,
-
-    // GLSL 4.50
-    C_derivative_control = 1ull << 31,
-    C_texture_query_samples = 1ull << 32,
-  };
-
-  static std::string format_stage(Stage stage);
-  static void output_capabilities(std::ostream &out, int capabilities);
-
   virtual void output(std::ostream &out) const;
 
   virtual void write_datagram(BamWriter *manager, Datagram &dg) override;
@@ -183,7 +113,7 @@ protected:
   //std::pvector<Filename> _source_files;
   Filename _source_filename;
   //time_t _source_modified = 0;
-  int _used_caps = 0;
+  uint64_t _used_caps = 0;
 
   typedef pvector<Variable> Variables;
   Variables _inputs;
@@ -221,10 +151,6 @@ INLINE std::ostream &operator << (std::ostream &out, const ShaderModule &module)
   return out;
 }
 
-INLINE std::ostream &operator << (std::ostream &out, ShaderModule::Stage stage) {
-  return out << ShaderModule::format_stage(stage);
-}
-
 #include "shaderModule.I"
 
 #endif

+ 1 - 1
panda/src/gsgbase/graphicsStateGuardianBase.h

@@ -121,7 +121,7 @@ PUBLISHED:
   virtual bool get_supports_compressed_texture_format(int compression_mode) const=0;
 
   virtual bool get_supports_multisample() const=0;
-  virtual int get_supported_shader_capabilities() const=0;
+  virtual uint64_t get_supported_shader_capabilities() const=0;
   virtual int get_supported_geom_rendering() const=0;
   virtual bool get_supports_shadow_filter() const=0;
 

+ 67 - 2
panda/src/pgraph/shaderAttrib.cxx

@@ -797,9 +797,74 @@ compose_impl(const RenderAttrib *other) const {
 }
 
 /**
- * Factory method to generate a Shader object
+ * Tells the BamReader how to create objects of type ShaderAttrib.
  */
 void ShaderAttrib::
 register_with_read_factory() {
-  // IMPLEMENT ME
+  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 ShaderAttrib::
+write_datagram(BamWriter *manager, Datagram &dg) {
+  RenderAttrib::write_datagram(manager, dg);
+
+  manager->write_pointer(dg, _shader);
+
+  dg.add_int32(_shader_priority);
+  dg.add_bool(_auto_shader);
+  dg.add_bool(_has_shader);
+  dg.add_int32(_flags);
+  dg.add_int32(_has_flags);
+  dg.add_int32(_instance_count);
+  dg.add_uint32(0);
+}
+
+/**
+ * Receives an array of pointers, one for each time manager->read_pointer()
+ * was called in fillin(). Returns the number of pointers processed.
+ */
+int ShaderAttrib::
+complete_pointers(TypedWritable **p_list, BamReader *manager) {
+  int pi = RenderAttrib::complete_pointers(p_list, manager);
+  _shader = DCAST(Shader, p_list[pi++]);
+  return pi;
+}
+
+/**
+ * This function is called by the BamReader's factory when a new object of
+ * type ShaderAttrib is encountered in the Bam file.  It should create the
+ * ShaderAttrib and extract its information from the file.
+ */
+TypedWritable *ShaderAttrib::
+make_from_bam(const FactoryParams &params) {
+  ShaderAttrib *attrib = new ShaderAttrib;
+  DatagramIterator scan;
+  BamReader *manager;
+
+  parse_params(params, scan, manager);
+  attrib->fillin(scan, manager);
+
+  return attrib;
+}
+
+/**
+ * This internal function is called by make_from_bam to read in all of the
+ * relevant data from the BamFile for the new ShaderAttrib.
+ */
+void ShaderAttrib::
+fillin(DatagramIterator &scan, BamReader *manager) {
+  RenderAttrib::fillin(scan, manager);
+
+  manager->read_pointer(scan);
+  _shader_priority = scan.get_int32();
+  _auto_shader = scan.get_bool();
+  _has_shader = scan.get_bool();
+  _flags = scan.get_int32();
+  _has_flags = scan.get_int32();
+  _instance_count = scan.get_int32();
+  scan.get_uint32();
 }

+ 9 - 2
panda/src/pgraph/shaderAttrib.h

@@ -124,8 +124,6 @@ PUBLISHED:
   const LMatrix4 &get_shader_input_matrix(const InternalName *id, LMatrix4 &matrix) const;
   ShaderBuffer *get_shader_input_buffer(const InternalName *id) const;
 
-  static void register_with_read_factory();
-
 PUBLISHED:
   MAKE_PROPERTY(shader, get_shader);
   MAKE_PROPERTY(instance_count, get_instance_count);
@@ -171,6 +169,15 @@ PUBLISHED:
   }
   MAKE_PROPERTY(class_slot, get_class_slot);
 
+public:
+  static void register_with_read_factory();
+  virtual void write_datagram(BamWriter *manager, Datagram &dg);
+  virtual int complete_pointers(TypedWritable **plist, BamReader *manager);
+
+protected:
+  static TypedWritable *make_from_bam(const FactoryParams &params);
+  void fillin(DatagramIterator &scan, BamReader *manager);
+
 public:
   static TypeHandle get_class_type() {
     return _type_handle;

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

@@ -45,9 +45,10 @@ init_libshaderpipeline() {
   initialized = true;
 
   ShaderCompilerGlslang::init_type();
-  ShaderModuleSpirV::init_type();
   ShaderModuleGlsl::init_type();
+  ShaderModuleSpirV::init_type();
 
+  ShaderModuleGlsl::register_with_read_factory();
   ShaderModuleSpirV::register_with_read_factory();
 
   ShaderCompilerRegistry *reg = ShaderCompilerRegistry::get_global_ptr();

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

@@ -49,7 +49,7 @@ ShaderCompiler::
  * ShaderModule on success.
  */
 PT(ShaderModule) ShaderCompiler::
-compile_now(ShaderModule::Stage stage, const Filename &fn, BamCacheRecord *record) const {
+compile_now(Stage stage, const Filename &fn, BamCacheRecord *record) const {
   VirtualFileSystem *vfs = VirtualFileSystem::get_global_ptr();
   PT(VirtualFile) vf = vfs->find_file(fn, get_model_path());
   if (vf == nullptr) {

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

@@ -26,15 +26,13 @@ typedef pvector<Shader::Shader::ShaderLanguage> ShaderLanguages;
 /**
  * This is the base class for objects to compile various types of shader code.
  */
-class EXPCL_PANDA_SHADERPIPELINE ShaderCompiler : public TypedObject {
+class EXPCL_PANDA_SHADERPIPELINE ShaderCompiler : public TypedObject, public ShaderEnums {
 protected:
   ShaderCompiler();
 
 public:
   virtual ~ShaderCompiler();
 
-  using Stage = ShaderModule::Stage;
-
 PUBLISHED:
   virtual std::string get_name() const=0;
   virtual ShaderLanguages get_languages() const=0;

+ 136 - 89
panda/src/shaderpipeline/shaderCompilerGlslPreProc.cxx

@@ -17,6 +17,33 @@
 
 #include "dcast.h"
 
+// Maps required extensions to shader caps, so we can do a slightly better job
+// at checking whether a given shader module is supported by the GSG.
+static struct ExtensionCaps { const char *ext; uint64_t caps; } _extension_caps[] = {
+  {"GL_ARB_compute_shader", ShaderEnums::C_compute_shader},
+  {"GL_ARB_derivative_control", ShaderEnums::C_derivative_control},
+  {"GL_ARB_draw_instanced", ShaderEnums::C_instance_id},
+  {"GL_ARB_enhanced_layouts", ShaderEnums::C_enhanced_layouts},
+  {"GL_ARB_geometry_shader4", ShaderEnums::C_geometry_shader},
+  {"GL_ARB_gpu_shader_fp64", ShaderEnums::C_double},
+  {"GL_ARB_shader_bit_encoding", ShaderEnums::C_bit_encoding},
+  {"GL_ARB_shader_image_load_store", ShaderEnums::C_image_load_store},
+  {"GL_ARB_shader_texture_lod", ShaderEnums::C_texture_lod},
+  {"GL_ARB_tessellation_shader", ShaderEnums::C_tessellation_shader},
+  {"GL_ARB_texture_buffer_object", ShaderEnums::C_texture_buffer},
+  {"GL_ARB_texture_query_levels", ShaderEnums::C_texture_query_levels},
+  {"GL_ARB_texture_query_lod", ShaderEnums::C_texture_query_lod},
+  {"GL_EXT_geometry_shader", ShaderEnums::C_geometry_shader},
+  {"GL_EXT_gpu_shader4", ShaderEnums::C_unified_model},
+  {"GL_EXT_shader_texture_lod", ShaderEnums::C_texture_lod},
+  {"GL_EXT_shadow_samplers", ShaderEnums::C_shadow_samplers},
+  {"GL_EXT_tessellation_shader", ShaderEnums::C_tessellation_shader},
+  {"GL_OES_sample_variables", ShaderEnums::C_sample_variables},
+  {"GL_OES_standard_derivatives", ShaderEnums::C_standard_derivatives},
+  {"GL_OES_texture_buffer", ShaderEnums::C_texture_buffer},
+  {nullptr, 0},
+};
+
 TypeHandle ShaderCompilerGlslPreProc::_type_handle;
 
 /**
@@ -50,11 +77,8 @@ get_languages() const {
  * ShaderModule on success.
  */
 PT(ShaderModule) ShaderCompilerGlslPreProc::
-compile_now(ShaderModule::Stage stage, std::istream &in,
-            const Filename &fullpath, BamCacheRecord *record) const {
-  PT(ShaderModuleGlsl) module = new ShaderModuleGlsl(stage);
-  std::string &into = module->_raw_source;
-
+compile_now(Stage stage, std::istream &in, const Filename &fullpath,
+            BamCacheRecord *record) const {
   // Create a name that's easier to read in error messages.
   std::string filename;
   if (fullpath.empty()) {
@@ -69,24 +93,26 @@ compile_now(ShaderModule::Stage stage, std::istream &in,
     }
   }
 
-  std::ostringstream sstr;
-  std::set<Filename> open_files;
-  if (r_preprocess_source(module, sstr, in, filename, fullpath, open_files, record)) {
-    into = sstr.str();
-
-    // Strip trailing whitespace.
-    while (!into.empty() && isspace(into[into.size() - 1])) {
-      into.resize(into.size() - 1);
-    }
-
-    // Except add back a newline at the end, which is needed by Intel drivers.
-    into += "\n";
+  State state;
+  if (!r_preprocess_source(state, in, filename, fullpath, record)) {
+    return nullptr;
+  }
 
-    module->_record = record;
-    return module;
-  } else {
+  if (!state.has_code) {
+    shader_cat.error()
+      << "GLSL shader " << filename << " does not contain any code!\n";
     return nullptr;
   }
+  if (!state.version) {
+    shader_cat.warning()
+      << "GLSL shader " << filename << " does not contain a #version line!\n";
+  }
+
+  PT(ShaderModuleGlsl) module = new ShaderModuleGlsl(stage, state.code.str(), state.version);
+  module->_included_files = std::move(state.included_files);
+  module->_used_caps |= state.required_caps;
+  module->_record = record;
+  return module;
 }
 
 /**
@@ -97,21 +123,20 @@ compile_now(ShaderModule::Stage stage, std::istream &in,
  * recursive includes.
  */
 bool ShaderCompilerGlslPreProc::
-r_preprocess_source(ShaderModuleGlsl *module,
-                    std::ostream &out, std::istream &in, const std::string &fn,
-                    const Filename &full_fn, std::set<Filename> &once_files,
-                    BamCacheRecord *record, int fileno, int depth) const {
+r_preprocess_source(State &state, std::istream &in, const std::string &fn,
+                    const Filename &full_fn, BamCacheRecord *record, int fileno,
+                    int depth) const {
 
   // Iterate over the lines for things we may need to preprocess.
   std::string line;
   int ext_google_include = 0; // 1 = warn, 2 = enable
   int ext_google_line = 0;
-  bool had_include = false;
-  bool had_version = false;
-  bool had_code = false;
+  bool had_nested_include = false;
   int lineno = 0;
   bool write_line_directive = (fileno != 0);
 
+  std::ostream &out = state.code;
+
   while (std::getline(in, line)) {
     ++lineno;
 
@@ -136,9 +161,8 @@ r_preprocess_source(ShaderModuleGlsl *module,
           out.put('\n');
         }
         ++lineno;
-      } else {
-        break;
       }
+      else break;
     }
 
     // Look for comments to strip.  This is necessary because comments may
@@ -148,8 +172,8 @@ r_preprocess_source(ShaderModuleGlsl *module,
     if (line_comment < block_comment) {
       // A line comment - strip off the rest of the line.
       line.resize(line_comment);
-
-    } else if (block_comment < line_comment) {
+    }
+    else if (block_comment < line_comment) {
       // A block comment.  Search for closing block.
       std::string line2 = line.substr(block_comment + 2);
 
@@ -198,7 +222,7 @@ r_preprocess_source(ShaderModuleGlsl *module,
         write_line_directive = false;
       }
       out << line << "\n";
-      had_code = true;
+      state.has_code = true;
       continue;
     }
 
@@ -217,14 +241,14 @@ r_preprocess_source(ShaderModuleGlsl *module,
             // A regular include, with double quotes.  Probably a local file.
             source_dir = full_fn.get_dirname();
             incfn = incfile;
-
-          } else if (sscanf(line.c_str(), " # pragma%*[ \t]include <%2047[^>]> %zn", incfile, &nread) == 1
+          }
+          else if (sscanf(line.c_str(), " # pragma%*[ \t]include <%2047[^>]> %zn", incfile, &nread) == 1
               && nread == line.size()) {
             // Angled includes are also OK, but we don't search in the directory
             // of the source file.
             incfn = incfile;
-
-          } else {
+          }
+          else {
             // Couldn't parse it.
             shader_cat.error()
               << "Malformed #pragma include at line " << lineno
@@ -234,7 +258,7 @@ r_preprocess_source(ShaderModuleGlsl *module,
         }
 
         // OK, great.  Process the include.
-        if (!r_preprocess_include(module, out, incfn, source_dir, once_files, record, depth + 1)) {
+        if (!r_preprocess_include(state, incfn, source_dir, record, depth + 1)) {
           // An error occurred.  Pass on the failure.
           shader_cat.error(false) << "included at line "
             << lineno << " of file " << fn << ":\n  " << line << "\n";
@@ -243,11 +267,13 @@ r_preprocess_source(ShaderModuleGlsl *module,
 
         // Restore the line counter.
         write_line_directive = true;
-        had_include = true;
-        had_code = true;
+        if (state.cond_nesting > 0) {
+          had_nested_include = true;
+        }
+        state.has_code = true;
         continue;
-
-      } else if (strcmp(pragma, "once") == 0) {
+      }
+      else if (strcmp(pragma, "once") == 0) {
         // Do a stricter syntax check, just to be extra safe.
         if (sscanf(line.c_str(), " # pragma%*[ \t]once %zn", &nread) != 0 ||
             nread != line.size()) {
@@ -268,36 +294,52 @@ r_preprocess_source(ShaderModuleGlsl *module,
         }
 
         if (!full_fn.empty()) {
-          once_files.insert(full_fn);
+          state.once_files.insert(full_fn);
         }
         continue;
       }
       // Otherwise, just pass it through to the driver.
-
-    } else if (strcmp(directive, "endif") == 0) {
+    }
+    else if (strncmp(directive, "if", 2) == 0) {
+      // Keep track of the level of conditional nesting.
+      state.cond_nesting++;
+    }
+    else if (strcmp(directive, "endif") == 0) {
       // Check for an #endif after an include.  We have to restore the line
       // number in case the include happened under an #if block.
-      if (had_include) {
+      if (had_nested_include) {
         write_line_directive = true;
       }
-
-    } else if (strcmp(directive, "version") == 0) {
-      had_version = true;
-
-    } else if (strcmp(directive, "extension") == 0) {
+      state.cond_nesting--;
+    }
+    else if (strcmp(directive, "version") == 0) {
+      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;
+      }
+    }
+    else if (strcmp(directive, "extension") == 0) {
       // Check for special preprocessing extensions.
       char extension[256];
       char behavior[9];
       if (sscanf(line.c_str(), " # extension%*[ \t]%255[^: \t] : %8s", extension, behavior) == 2) {
         // Parse the behavior string.
         int mode;
-        if (strcmp(behavior, "require") == 0 || strcmp(behavior, "enable") == 0) {
+        if (strcmp(behavior, "require") == 0) {
+          mode = 3;
+        }
+        else if (strcmp(behavior, "enable") == 0) {
           mode = 2;
-        } else if (strcmp(behavior, "warn") == 0) {
+        }
+        else if (strcmp(behavior, "warn") == 0) {
           mode = 1;
-        } else if (strcmp(behavior, "disable") == 0) {
+        }
+        else if (strcmp(behavior, "disable") == 0) {
           mode = 0;
-        } else {
+        }
+        else {
           shader_cat.error()
             << "Extension directive specifies invalid behavior at line "
             << lineno << " of file " << fn << ":\n  " << line << "\n";
@@ -305,7 +347,7 @@ r_preprocess_source(ShaderModuleGlsl *module,
         }
 
         if (strcmp(extension, "all") == 0) {
-          if (mode == 2) {
+          if (mode >= 2) {
             shader_cat.error()
               << "Extension directive for 'all' may only specify 'warn' or "
                  "'disable' at line " << lineno << " of file " << fn
@@ -316,28 +358,44 @@ r_preprocess_source(ShaderModuleGlsl *module,
           ext_google_line = mode;
           // Still pass it through to the driver, so it can enable other
           // extensions.
-
-        } else if (strcmp(extension, "GL_GOOGLE_include_directive") == 0) {
+        }
+        else if (strcmp(extension, "GL_GOOGLE_include_directive") == 0) {
           // Enable the Google extension support for #include statements.
           // This also implicitly enables GL_GOOGLE_cpp_style_line_directive.
           // This matches the behavior of Khronos' glslang reference compiler.
           ext_google_include = mode;
           ext_google_line = mode;
           continue;
-
-        } else if (strcmp(extension, "GL_GOOGLE_cpp_style_line_directive") == 0) {
+        }
+        else if (strcmp(extension, "GL_GOOGLE_cpp_style_line_directive") == 0) {
           // Enables strings in #line statements.
           ext_google_line = mode;
           continue;
         }
-      } else {
+        else if (mode == 3 && state.cond_nesting == 0) {
+          // Pick up the capabilities used by this extension.
+          ExtensionCaps *caps = _extension_caps;
+          while (caps->ext) {
+            int result = strcmp(extension, caps->ext);
+            if (result < 0) {
+              break;
+            }
+            if (result == 0) {
+              state.required_caps |= caps->caps;
+              break;
+            }
+            ++caps;
+          }
+        }
+      }
+      else {
         shader_cat.error()
           << "Failed to parse extension directive at line "
           << lineno << " of file " << fn << ":\n  " << line << "\n";
         return false;
       }
-
-    } else if (ext_google_include > 0 && strcmp(directive, "include") == 0) {
+    }
+    else if (ext_google_include > 0 && strcmp(directive, "include") == 0) {
       // Warn about extension use if requested.
       if (ext_google_include == 1) {
         shader_cat.warning()
@@ -366,7 +424,7 @@ r_preprocess_source(ShaderModuleGlsl *module,
 
       // OK, great.  Process the include.
       Filename source_dir = full_fn.get_dirname();
-      if (!r_preprocess_include(module, out, incfn, source_dir, once_files, record, depth + 1)) {
+      if (!r_preprocess_include(state, incfn, source_dir, record, depth + 1)) {
         // An error occurred.  Pass on the failure.
         shader_cat.error(false) << "included at line "
           << lineno << " of file " << fn << ":\n  " << line << "\n";
@@ -375,11 +433,13 @@ r_preprocess_source(ShaderModuleGlsl *module,
 
       // Restore the line counter.
       write_line_directive = true;
-      had_include = true;
-      had_code = true;
+      if (state.cond_nesting > 0) {
+        had_nested_include = true;
+      }
+      state.has_code = true;
       continue;
-
-    } else if (ext_google_line > 0 && strcmp(directive, "line") == 0) {
+    }
+    else if (ext_google_line > 0 && strcmp(directive, "line") == 0) {
       // It's a #line directive.  See if it uses a string instead of number.
       char filestr[2048];
       if (sscanf(line.c_str(), " # line%*[ \t]%d%*[ \t]\"%2047[^\"]\" %zn", &lineno, filestr, &nread) == 2
@@ -397,7 +457,8 @@ r_preprocess_source(ShaderModuleGlsl *module,
 
         // Replace the string line number with an integer.  This is something
         // we can substitute later when parsing the GLSL log from the driver.
-        fileno = module->add_included_file(filestr);
+        fileno = ShaderModuleGlsl::_fileno_offset + (int)state.included_files.size();
+        state.included_files.push_back(filestr);
         out << "#line " << lineno << " " << fileno << " // " << filestr << "\n";
         continue;
       }
@@ -410,22 +471,9 @@ r_preprocess_source(ShaderModuleGlsl *module,
     out << line << "\n";
   }
 
-  if (fileno == 0) {
-    if (!had_code) {
-      shader_cat.error()
-        << "GLSL shader " << fn << " does not contain any code!\n";
-      return false;
-    }
-    if (!had_version) {
-      shader_cat.warning()
-        << "GLSL shader " << fn << " does not contain a #version line!\n";
-    }
-  }
-
   return true;
 }
 
-
 /**
  * Loads a given GLSL file line by line, and processes any #pragma include and
  * once statements, as well as removes any comments.
@@ -434,11 +482,9 @@ r_preprocess_source(ShaderModuleGlsl *module,
  * recursive includes.
  */
 bool ShaderCompilerGlslPreProc::
-r_preprocess_include(ShaderModuleGlsl *module,
-                     std::ostream &out, const std::string &fn,
-                     const Filename &source_dir,
-                     std::set<Filename> &once_files,
-                     BamCacheRecord *record, int depth) const {
+r_preprocess_include(State &state, const std::string &fn,
+                     const Filename &source_dir, BamCacheRecord *record,
+                     int depth) const {
 
   if (depth > glsl_include_recursion_limit) {
     shader_cat.error()
@@ -461,7 +507,7 @@ r_preprocess_include(ShaderModuleGlsl *module,
   }
 
   Filename full_fn = vf->get_filename();
-  if (once_files.find(full_fn) != once_files.end()) {
+  if (state.once_files.find(full_fn) != state.once_files.end()) {
     // If this file had a #pragma once, just move on.
     return true;
   }
@@ -488,14 +534,15 @@ r_preprocess_include(ShaderModuleGlsl *module,
   // Write it into the vector so that we can substitute it later when we are
   // parsing the GLSL error log.  Don't store the full filename because it
   // would just be too long to display.
-  int fileno = module->add_included_file(fn);
+  int fileno = ShaderModuleGlsl::_fileno_offset + (int)state.included_files.size();
+  state.included_files.push_back(fn);
 
   if (shader_cat.is_debug()) {
     shader_cat.debug()
       << "Preprocessing shader include " << fileno << ": " << fn << "\n";
   }
 
-  bool result = r_preprocess_source(module, out, *source, fn, full_fn, once_files, record, fileno, depth);
+  bool result = r_preprocess_source(state, *source, fn, full_fn, record, fileno, depth);
   vf->close_read_file(source);
   return result;
 }

+ 15 - 10
panda/src/shaderpipeline/shaderCompilerGlslPreProc.h

@@ -34,17 +34,22 @@ public:
                                        BamCacheRecord *record = nullptr) const override;
 
 private:
-  bool r_preprocess_include(ShaderModuleGlsl *module,
-                            std::ostream &out, const std::string &filename,
-                            const Filename &source_dir,
-                            std::set<Filename> &open_files,
-                            BamCacheRecord *record, int depth) const;
-  bool r_preprocess_source(ShaderModuleGlsl *module,
-                           std::ostream &out, std::istream &in,
+  struct State {
+    std::ostringstream code;
+    std::set<Filename> once_files;
+    pvector<Filename> included_files;
+    uint64_t required_caps = 0;
+    int cond_nesting = 0;
+    int version = 0;
+    bool has_code = false;
+  };
+  bool r_preprocess_include(State &state, const std::string &filename,
+                            const Filename &source_dir, BamCacheRecord *record,
+                            int depth) const;
+  bool r_preprocess_source(State &state, std::istream &in,
                            const std::string &fn, const Filename &full_fn,
-                           std::set<Filename> &open_files,
-                           BamCacheRecord *record,
-                           int fileno = 0, int depth = 0) const;
+                           BamCacheRecord *record = nullptr, int fileno = 0,
+                           int depth = 0) const;
 
 public:
   static TypeHandle get_class_type() {

+ 83 - 7
panda/src/shaderpipeline/shaderModuleGlsl.cxx

@@ -17,9 +17,22 @@
 TypeHandle ShaderModuleGlsl::_type_handle;
 
 ShaderModuleGlsl::
-ShaderModuleGlsl(Stage stage) :
-  ShaderModule(stage)
+ShaderModuleGlsl(Stage stage, std::string source, int version) :
+  ShaderModule(stage),
+  _code(std::move(source)),
+  _version(0)
 {
+  if (version >= 130) { // also matches ESSL 3.00+
+    _used_caps |= C_unified_model;
+  }
+
+  // Strip trailing whitespace.
+  while (!_code.empty() && isspace(_code[_code.size() - 1])) {
+    _code.resize(_code.size() - 1);
+  }
+
+  // Except add back a newline at the end, which is needed by Intel drivers.
+  _code += "\n";
 }
 
 ShaderModuleGlsl::
@@ -36,7 +49,7 @@ make_cow_copy() {
 
 std::string ShaderModuleGlsl::
 get_ir() const {
-  return this->_raw_source;
+  return this->_code;
 }
 
 /**
@@ -46,7 +59,7 @@ get_ir() const {
  */
 int ShaderModuleGlsl::
 add_included_file(Filename fn) {
-  int fileno = 2048 + _included_files.size();
+  int fileno = _fileno_offset + _included_files.size();
   _included_files.push_back(std::move(fn));
   return fileno;
 }
@@ -63,12 +76,75 @@ get_filename_from_index(int index) const {
     if (!fn.empty()) {
       return fn;
     }
-  } else if (glsl_preprocess && index >= 2048 &&
-             (index - 2048) < (int)_included_files.size()) {
-    return _included_files[(size_t)index - 2048];
+  }
+  else if (index >= _fileno_offset && (index - _fileno_offset) < (int)_included_files.size()) {
+    return _included_files[(size_t)index - _fileno_offset];
   }
 
   // Must be a mistake.  Quietly put back the integer.
   std::string str = format_string(index);
   return Filename(str);
 }
+
+/**
+ * Tells the BamReader how to create objects of type ShaderModuleGlsl.
+ */
+void ShaderModuleGlsl::
+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 ShaderModuleGlsl::
+write_datagram(BamWriter *manager, Datagram &dg) {
+  ShaderModule::write_datagram(manager, dg);
+
+  dg.add_int16(_version);
+  dg.add_string32(_code);
+
+  dg.add_uint16(_included_files.size());
+  for (const Filename &fn : _included_files) {
+    dg.add_string(fn);
+  }
+}
+
+/**
+ * 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 *ShaderModuleGlsl::
+make_from_bam(const FactoryParams &params) {
+  DatagramIterator scan;
+  BamReader *manager;
+
+  parse_params(params, scan, manager);
+
+  Stage stage = (Stage)scan.get_uint8();
+  ShaderModuleGlsl *module = new ShaderModuleGlsl(stage, std::string(), 0);
+  module->fillin(scan, manager);
+
+  return module;
+}
+
+/**
+ * This internal function is called by make_from_bam to read in all of the
+ * relevant data from the BamFile for the new ShaderModuleGlsl.
+ */
+void ShaderModuleGlsl::
+fillin(DatagramIterator &scan, BamReader *manager) {
+  _source_filename = scan.get_string();
+  _used_caps = (int)scan.get_uint64();
+
+  _version = scan.get_int16();
+  _code = scan.get_string32();
+
+  size_t num_included_files = scan.get_uint16();
+  _included_files.resize(num_included_files);
+  for (size_t i = 0; i < num_included_files; ++i) {
+    _included_files[i] = scan.get_string();
+  }
+}

+ 14 - 3
panda/src/shaderpipeline/shaderModuleGlsl.h

@@ -20,10 +20,11 @@
  * ShaderModule that contains raw, preprocessed GLSL code
  */
 class EXPCL_PANDA_SHADERPIPELINE ShaderModuleGlsl final : public ShaderModule {
-public:
-  ShaderModuleGlsl(Stage stage);
+PUBLISHED:
+  ShaderModuleGlsl(Stage stage, std::string code, int version = 0);
   virtual ~ShaderModuleGlsl();
 
+public:
   virtual PT(CopyOnWriteObject) make_cow_copy() override;
 
   virtual std::string get_ir() const override;
@@ -32,13 +33,23 @@ public:
   Filename get_filename_from_index(int index) const;
 
 protected:
-  std::string _raw_source;
+  std::string _code;
+  int _version = 0;
 
+  static const int _fileno_offset = 2048;
   typedef pvector<Filename> Filenames;
   Filenames _included_files;
 
   friend class ShaderCompilerGlslPreProc;
 
+public:
+  static void register_with_read_factory();
+  virtual void write_datagram(BamWriter *manager, Datagram &dg) override;
+
+protected:
+  static TypedWritable *make_from_bam(const FactoryParams &params);
+  void fillin(DatagramIterator &scan, BamReader *manager) override;
+
 public:
   static TypeHandle get_class_type() {
     return _type_handle;

+ 217 - 61
panda/src/shaderpipeline/shaderModuleSpirV.cxx

@@ -46,18 +46,6 @@ ShaderModuleSpirV(Stage stage, std::vector<uint32_t> words, BamCacheRecord *reco
   for (InstructionIterator it = _instructions.begin(); it != _instructions.begin_annotations(); ++it) {
     Instruction op = *it;
     switch (op.opcode) {
-    case spv::OpExtInst:
-      {
-        const Definition &def = writer.get_definition(op.args[2]);
-        nassertv(def._dtype == DT_ext_inst);
-        if (def._name == "GLSL.std.450" && op.args[3] == GLSLstd450RoundEven) {
-          // We mark the use of the GLSL roundEven() function, which requires
-          // GLSL 1.30 or HLSL SM 4.0.
-          _used_caps |= C_round_even;
-        }
-      }
-      break;
-
     case spv::OpMemoryModel:
       if (op.args[0] != spv::AddressingModelLogical) {
         shader_cat.error()
@@ -71,39 +59,51 @@ ShaderModuleSpirV(Stage stage, std::vector<uint32_t> words, BamCacheRecord *reco
       }
       break;
 
+    case spv::OpExecutionMode:
+      if (op.nargs >= 3 && op.args[1] == spv::ExecutionModeInvocations) {
+        if (op.args[2] != 1) {
+          _used_caps |= C_geometry_shader_instancing;
+        }
+      }
+      break;
+
     case spv::OpCapability:
       switch ((spv::Capability)op.args[0]) {
       case spv::CapabilityFloat64:
         _used_caps |= C_double;
         break;
 
-      case spv::CapabilityImageGatherExtended:
-        _used_caps |= C_texture_gather;
+      case spv::CapabilityAtomicStorage:
+        _used_caps |= C_atomic_counters;
+        break;
+
+      case spv::CapabilityClipDistance:
+        _used_caps |= C_clip_distance;
+        break;
+
+      case spv::CapabilityCullDistance:
+        _used_caps |= C_cull_distance;
         break;
 
       case spv::CapabilityImageCubeArray:
+      case spv::CapabilitySampledCubeArray:
         _used_caps |= C_cube_map_array;
         break;
 
+      case spv::CapabilitySampledBuffer:
+      case spv::CapabilityImageBuffer:
+        _used_caps |= C_texture_buffer;
+        break;
+
+      case spv::CapabilityDerivativeControl:
+        _used_caps |= C_derivative_control;
+        break;
+
       default:
         break;
       }
       break;
 
-    case spv::OpConvertFToU:
-    case spv::OpConvertUToF:
-    case spv::OpUConvert:
-    case spv::OpUDiv:
-    case spv::OpUMod:
-    case spv::OpUMulExtended:
-    case spv::OpUGreaterThan:
-    case spv::OpUGreaterThanEqual:
-    case spv::OpULessThan:
-    case spv::OpULessThanEqual:
-    case spv::OpShiftRightLogical:
-      _used_caps |= C_unsigned_int;
-      break;
-
     default:
       break;
     }
@@ -130,14 +130,24 @@ ShaderModuleSpirV(Stage stage, std::vector<uint32_t> words, BamCacheRecord *reco
   for (uint32_t id = 0; id < _instructions.get_id_bound(); ++id) {
     const Definition &def = writer.get_definition(id);
 
-    if (def.is_used() && def._type != nullptr &&
-        def._type->contains_scalar_type(ShaderType::ST_double)) {
-      _used_caps |= C_double;
+    if (def.is_used() && def._type != nullptr) {
+      if (def._type->contains_scalar_type(ShaderType::ST_double)) {
+        _used_caps |= C_double;
+      }
+      if (const ShaderType::Matrix *matrix_type = def._type->as_matrix()) {
+        if (matrix_type->get_num_rows() != matrix_type->get_num_columns()) {
+          _used_caps |= C_non_square_matrices;
+        }
+      }
+      if (def._type->contains_scalar_type(ShaderType::ST_uint)) {
+        _used_caps |= C_unified_model;
+      }
     }
 
     if (def._dtype == DT_variable && !def.is_builtin()) {
       // Ignore empty structs/arrays.
-      if (def._type->get_num_interface_locations() == 0) {
+      int num_locations = def._type->get_num_interface_locations();
+      if (num_locations == 0) {
         continue;
       }
 
@@ -151,15 +161,20 @@ ShaderModuleSpirV(Stage stage, std::vector<uint32_t> words, BamCacheRecord *reco
         _inputs.push_back(std::move(var));
 
         if (stage == Stage::fragment) {
-          // Integer varyings are implicitly flat-interpolated.
+          // Integer varyings require shader model 4.
           if (def._type->contains_scalar_type(ShaderType::ST_uint) ||
               def._type->contains_scalar_type(ShaderType::ST_int) ||
               def._type->contains_scalar_type(ShaderType::ST_bool)) {
-            _used_caps |= C_flat_interpolation;
+            _used_caps |= C_unified_model;
           }
         }
       }
       else if (def._storage_class == spv::StorageClassOutput) {
+        if (!_outputs.empty() || num_locations > 1) {
+          // This shader requires MRT.
+          _used_caps |= C_draw_buffers;
+        }
+
         _outputs.push_back(std::move(var));
       }
       else if (def._storage_class == spv::StorageClassUniformConstant) {
@@ -181,20 +196,61 @@ ShaderModuleSpirV(Stage stage, std::vector<uint32_t> words, BamCacheRecord *reco
           }
           if (sampled_image_type->get_sampled_type() == ShaderType::ST_uint ||
               sampled_image_type->get_sampled_type() == ShaderType::ST_int) {
-            _used_caps |= C_int_samplers;
+            _used_caps |= C_texture_integer;
+          }
+          switch (sampled_image_type->get_texture_type()) {
+          case Texture::TT_buffer_texture:
+            _used_caps |= C_texture_buffer;
+            break;
+          case Texture::TT_cube_map_array:
+            _used_caps |= C_cube_map_array;
+            // fall through
+          case Texture::TT_1d_texture_array:
+          case Texture::TT_2d_texture_array:
+            _used_caps |= C_texture_array;
+            break;
           }
         }
         _parameters.push_back(std::move(var));
       }
-
-      if (def._type->contains_scalar_type(ShaderType::ST_uint)) {
-        _used_caps |= C_unsigned_int;
+      else if (def._storage_class == spv::StorageClassStorageBuffer) {
+        _used_caps |= C_storage_buffer;
+      }
+      else if (def._storage_class == spv::StorageClassUniform) {
+        // Older versions of SPIR-V defined SSBOs differently.
+        const Definition &type_pointer_def = writer.get_definition(def._type_id);
+        nassertd(type_pointer_def._dtype == DT_type_pointer) continue;
+        const Definition &type_def = writer.get_definition(type_pointer_def._type_id);
+        nassertd(type_def._dtype == DT_type) continue;
+        if (type_def._flags & DF_buffer_block) {
+          _used_caps |= C_storage_buffer;
+        }
       }
     }
     else if (def._dtype == DT_variable && def.is_used() &&
              def._storage_class == spv::StorageClassInput) {
       // Built-in input variable.
       switch (def._builtin) {
+      case spv::BuiltInClipDistance:
+        if ((_used_caps & C_clip_distance) == 0) {
+          shaderpipeline_cat.warning()
+            << "Shader uses " << "ClipDistance"
+            << ", but does not declare capability!\n";
+
+          _used_caps |= C_clip_distance;
+        }
+        break;
+
+      case spv::BuiltInCullDistance:
+        if ((_used_caps & C_cull_distance) == 0) {
+          shaderpipeline_cat.warning()
+            << "Shader uses " << "CullDistance"
+            << ", but does not declare capability!\n";
+
+          _used_caps |= C_cull_distance;
+        }
+        break;
+
       case spv::BuiltInVertexId:
         _used_caps |= C_vertex_id;
         break;
@@ -207,6 +263,10 @@ ShaderModuleSpirV(Stage stage, std::vector<uint32_t> words, BamCacheRecord *reco
         _used_caps |= C_primitive_id;
         break;
 
+      case spv::BuiltInPointCoord:
+        _used_caps |= C_point_coord;
+        break;
+
       case spv::BuiltInSampleId:
       case spv::BuiltInSampleMask:
       case spv::BuiltInSamplePosition:
@@ -249,24 +309,53 @@ ShaderModuleSpirV(Stage stage, std::vector<uint32_t> words, BamCacheRecord *reco
   // from the module.
   strip();
 
-  // Check for more caps.
-  for (InstructionIterator it = _instructions.begin_functions(); it != _instructions.end(); ++it) {
+  // Check for more caps, now that we've optimized the module.
+  for (InstructionIterator it = _instructions.begin_annotations(); it != _instructions.end_annotations(); ++it) {
     Instruction op = *it;
-    switch (op.opcode) {
-    case spv::OpDecorate:
+    if (op.opcode == spv::OpDecorate) {
+      if (writer.get_definition(op.args[0]).is_builtin()) {
+        continue;
+      }
       switch ((spv::Decoration)op.args[1]) {
       case spv::DecorationNoPerspective:
         _used_caps |= C_noperspective_interpolation;
         break;
       case spv::DecorationFlat:
-        _used_caps |= C_flat_interpolation;
+        _used_caps |= C_unified_model;
+        break;
+      case spv::DecorationSample:
+        _used_caps |= C_multisample_interpolation;
         break;
       case spv::DecorationInvariant:
-        _used_caps |= C_invariant;
+        //_used_caps |= C_invariant;
         break;
+      case spv::DecorationComponent:
+        _used_caps |= C_enhanced_layouts;
+        break;
+      }
+    }
+  }
+
+  for (InstructionIterator it = _instructions.begin_functions(); it != _instructions.end(); ++it) {
+    Instruction op = *it;
+    switch (op.opcode) {
+    case spv::OpExtInst:
+      {
+        const Definition &def = writer.get_definition(op.args[2]);
+        nassertv(def._dtype == DT_ext_inst);
+        if (def._name == "GLSL.std.450" && op.args[3] == GLSLstd450RoundEven) {
+          // We mark the use of the GLSL roundEven() function, which requires
+          // GLSL 1.30 or HLSL SM 4.0.
+          _used_caps |= C_unified_model;
+        }
       }
       break;
 
+    case spv::OpImageTexelPointer:
+      // These can only be used for atomic ops.
+      _used_caps |= C_image_load_store | C_image_atomic;
+      break;
+
     case spv::OpImageRead:
     case spv::OpImageWrite:
     case spv::OpImageSparseRead:
@@ -276,7 +365,6 @@ ShaderModuleSpirV(Stage stage, std::vector<uint32_t> words, BamCacheRecord *reco
     case spv::OpImageSampleExplicitLod:
     case spv::OpImageSampleProjExplicitLod:
       if (stage != Stage::vertex) {
-        // TODO: check grad
         _used_caps |= C_texture_lod;
       }
       // fall through
@@ -285,18 +373,20 @@ ShaderModuleSpirV(Stage stage, std::vector<uint32_t> words, BamCacheRecord *reco
       if (stage == Stage::vertex) {
         _used_caps |= C_vertex_texture;
       }
+      if (op.nargs >= 5 && (op.args[4] & spv::ImageOperandsGradMask) != 0) {
+        _used_caps |= C_texture_lod;
+      }
       break;
 
     case spv::OpImageSampleDrefExplicitLod:
     case spv::OpImageSampleProjDrefExplicitLod:
       if (stage != Stage::vertex) {
-        // TODO: check grad
         _used_caps |= C_texture_lod;
       }
       // fall through
     case spv::OpImageSampleDrefImplicitLod:
     case spv::OpImageSampleProjDrefImplicitLod:
-      _used_caps |= C_sampler_shadow;
+      _used_caps |= C_shadow_samplers;
 
       {
         const Definition &sampler_def = writer.get_definition(op.args[2]);
@@ -311,16 +401,42 @@ ShaderModuleSpirV(Stage stage, std::vector<uint32_t> words, BamCacheRecord *reco
       if (stage == Stage::vertex) {
         _used_caps |= C_vertex_texture;
       }
+      if (op.nargs >= 5 && (op.args[4] & spv::ImageOperandsGradMask) != 0) {
+        _used_caps |= C_texture_lod;
+      }
       break;
 
     case spv::OpImageFetch:
+      _used_caps |= C_unified_model;
+      break;
+
     case spv::OpImageQuerySizeLod:
     case spv::OpImageQuerySize:
-      _used_caps |= C_texture_fetch;
+      {
+        const Definition &image_def = writer.get_definition(op.args[2]);
+        if (image_def._type != nullptr && image_def._type->as_image() != nullptr) {
+          _used_caps |= C_image_query_size;
+        } else {
+          _used_caps |= C_texture_query_size;
+        }
+      }
       break;
 
     case spv::OpImageGather:
-      _used_caps |= C_texture_gather;
+    case spv::OpImageSparseGather:
+      {
+        const Definition &component = writer.get_definition(op.args[4]);
+        if (component._dtype == DT_constant && component._constant == 0) {
+          _used_caps |= C_texture_gather_red;
+        } else {
+          _used_caps |= C_texture_gather_any;
+        }
+      }
+      break;
+
+    case spv::OpImageDrefGather:
+    case spv::OpImageSparseDrefGather:
+      _used_caps |= C_texture_gather_any;
       break;
 
     case spv::OpImageQueryLod:
@@ -339,9 +455,25 @@ ShaderModuleSpirV(Stage stage, std::vector<uint32_t> words, BamCacheRecord *reco
       _used_caps |= C_bit_encoding;
       break;
 
+    case spv::OpConvertFToU:
+    case spv::OpConvertUToF:
+    case spv::OpUConvert:
+    case spv::OpUDiv:
+    case spv::OpUMod:
+    case spv::OpUMulExtended:
+    case spv::OpUGreaterThan:
+    case spv::OpUGreaterThanEqual:
+    case spv::OpULessThan:
+    case spv::OpULessThanEqual:
+    case spv::OpShiftRightLogical:
+      _used_caps |= C_unified_model;
+      if (op.opcode != spv::OpUMulExtended) {
+        break;
+      }
+      // fall through
+
     case spv::OpIAddCarry:
     case spv::OpISubBorrow:
-    case spv::OpUMulExtended:
     case spv::OpSMulExtended:
       _used_caps |= C_extended_arithmetic;
       break;
@@ -353,6 +485,12 @@ ShaderModuleSpirV(Stage stage, std::vector<uint32_t> words, BamCacheRecord *reco
     case spv::OpDPdyCoarse:
     case spv::OpFwidthCoarse:
       _used_caps |= C_derivative_control;
+      // fall through
+
+    case spv::OpDPdx:
+    case spv::OpDPdy:
+    case spv::OpFwidth:
+      _used_caps |= C_standard_derivatives;
       break;
 
     default:
@@ -380,10 +518,12 @@ get_ir() const {
 
 /**
  * Links the stage with the given previous stage, by matching up its inputs with
- * the outputs of the previous stage and assigning locations.
+ * the outputs of the previous stage.  Rather than reassigning the locations
+ * directly, this method just returns the location remappings that need to be
+ * made, or returns false if the stages cannot be linked.
  */
 bool ShaderModuleSpirV::
-link_inputs(const ShaderModule *previous) {
+link_inputs(const ShaderModule *previous, pmap<int, int> &remap) const {
   if (!previous->is_of_type(ShaderModuleSpirV::get_class_type())) {
     return false;
   }
@@ -391,11 +531,9 @@ link_inputs(const ShaderModule *previous) {
     return false;
   }
 
-  pmap<int, int> location_remap;
-
   ShaderModuleSpirV *spv_prev = (ShaderModuleSpirV *)previous;
 
-  for (Variable &input : _inputs) {
+  for (const Variable &input : _inputs) {
     int i = spv_prev->find_output(input.name);
     if (i < 0) {
       shader_cat.error()
@@ -413,14 +551,10 @@ link_inputs(const ShaderModule *previous) {
     }
 
     if (!input.has_location() || output.get_location() != input.get_location()) {
-      location_remap[input.get_location()] = output.get_location();
-      input._location = output.get_location();
+      remap[input.get_location()] = output.get_location();
     }
   }
 
-  if (!location_remap.empty()) {
-    remap_locations(spv::StorageClassInput, location_remap);
-  }
   return true;
 }
 
@@ -429,7 +563,25 @@ link_inputs(const ShaderModule *previous) {
  * not included in the map remain untouched.
  */
 void ShaderModuleSpirV::
-remap_parameter_locations(pmap<int, int> &locations) {
+remap_input_locations(const pmap<int, int> &locations) {
+  remap_locations(spv::StorageClassInput, locations);
+
+  for (Variable &input : _inputs) {
+    if (input.has_location()) {
+      pmap<int, int>::const_iterator it = locations.find(input.get_location());
+      if (it != locations.end()) {
+        input._location = it->second;
+      }
+    }
+  }
+}
+
+/**
+ * Remaps parameters with a given location to a given other location.  Locations
+ * not included in the map remain untouched.
+ */
+void ShaderModuleSpirV::
+remap_parameter_locations(const pmap<int, int> &locations) {
   remap_locations(spv::StorageClassUniformConstant, locations);
 
   // If we extracted out the parameters, replace the locations there as well.
@@ -2576,6 +2728,10 @@ parse_instruction(const Instruction &op, uint32_t &current_function_id) {
 
   case spv::OpDecorate:
     switch ((spv::Decoration)op.args[1]) {
+    case spv::DecorationBufferBlock:
+      _defs[op.args[0]]._flags |= DF_buffer_block;
+      break;
+
     case spv::DecorationBuiltIn:
       _defs[op.args[0]]._builtin = (spv::BuiltIn)op.args[2];
       break;

+ 6 - 2
panda/src/shaderpipeline/shaderModuleSpirV.h

@@ -37,8 +37,9 @@ public:
   INLINE const uint32_t *get_data() const;
   INLINE size_t get_data_size() const;
 
-  virtual bool link_inputs(const ShaderModule *previous) override;
-  virtual void remap_parameter_locations(pmap<int, int> &remap) override;
+  virtual bool link_inputs(const ShaderModule *previous, pmap<int, int> &remap) const override;
+  virtual void remap_input_locations(const pmap<int, int> &remap) override;
+  virtual void remap_parameter_locations(const pmap<int, int> &remap) override;
 
   virtual std::string get_ir() const override;
 
@@ -140,6 +141,9 @@ public:
     // respectively one with and without depth comparison
     DF_dref_sampled = 4,
     DF_non_dref_sampled = 8,
+
+    // Has the "buffer block" decoration (older versions of SPIR-V).
+    DF_buffer_block = 16,
   };
 
   /**

+ 662 - 0
tests/shaderpipeline/test_glsl_caps.py

@@ -0,0 +1,662 @@
+from panda3d import core
+
+Shader = core.Shader
+Stage = core.Shader.Stage
+
+
+def compile_and_get_caps(stage, code):
+    registry = core.ShaderCompilerRegistry.get_global_ptr()
+    compiler = registry.get_compiler_from_language(Shader.SL_GLSL)
+    stream = core.StringStream(code.encode('ascii'))
+    module = compiler.compile_now(stage, stream, 'test-shader')
+    assert module
+    assert module.stage == stage
+
+    # Mask out this bit, every shader should have it
+    assert (module.used_capabilities & Shader.C_basic_shader) != 0
+    return module.used_capabilities & ~Shader.C_basic_shader
+
+
+def test_legacy_glsl_caps_basic_vertex():
+    assert compile_and_get_caps(Stage.vertex, """
+    #version 120
+
+    void main() {
+        gl_Position = vec4(0.0);
+    }
+    """) == 0
+
+
+def test_legacy_glsl_caps_basic_fragment():
+    assert compile_and_get_caps(Stage.vertex, """
+    #version 120
+
+    void main() {
+        gl_FragColor = vec4(0.0);
+    }
+    """) == 0
+
+
+def test_legacy_glsl_caps_unified_model():
+    assert compile_and_get_caps(Stage.vertex, """
+    #version 130
+
+    void main() {
+        gl_Position = vec4(0.0);
+    }
+    """) == Shader.C_unified_model
+
+    # Optional extension doesn't trigger it
+    assert compile_and_get_caps(Stage.vertex, """
+    #version 120
+
+    #extension GL_EXT_gpu_shader4 : enable
+
+    void main() {
+        gl_Position = vec4(0.0);
+    }
+    """) == 0
+
+    # Required extension does
+    assert compile_and_get_caps(Stage.vertex, """
+    #version 120
+
+    #extension GL_EXT_gpu_shader4 : require
+
+    void main() {
+        gl_Position = vec4(0.0);
+    }
+    """) == Shader.C_unified_model
+
+    # Extension under #if doesn't either
+    assert compile_and_get_caps(Stage.vertex, """
+    #version 120
+
+    #if foobar
+    #extension GL_EXT_gpu_shader4 : require
+    #endif
+
+    void main() {
+        gl_Position = vec4(0.0);
+    }
+    """) == 0
+
+
+def test_glsl_caps_basic_vertex():
+    assert compile_and_get_caps(Stage.vertex, """
+    #version 330
+
+    void main() {
+        gl_Position = vec4(0.0);
+    }
+    """) == 0
+
+
+def test_glsl_caps_basic_fragment():
+    assert compile_and_get_caps(Stage.fragment, """
+    #version 330
+
+    out vec4 p3d_FragColor;
+
+    void main() {
+        p3d_FragColor = vec4(1.0);
+    }
+    """) == 0
+
+
+def test_glsl_caps_vertex_texture():
+    assert compile_and_get_caps(Stage.vertex, """
+    #version 330
+
+    uniform sampler2D tex;
+
+    void main() {
+        gl_Position = textureLod(tex, vec2(0.0), 0);
+    }
+    """) == Shader.C_vertex_texture
+
+
+def test_glsl_caps_point_coord():
+    assert compile_and_get_caps(Stage.fragment, """
+    #version 330
+
+    out vec4 p3d_FragColor;
+
+    void main() {
+        p3d_FragColor = vec4(gl_PointCoord, 0.0, 1.0);
+    }
+    """) == Shader.C_point_coord
+
+
+def test_glsl_caps_standard_derivatives():
+    assert compile_and_get_caps(Stage.fragment, """
+    #version 330
+
+    out vec4 p3d_FragColor;
+
+    void main() {
+        p3d_FragColor = vec4(fwidth(gl_FragCoord.x), 0.0, 0.0, 1.0);
+    }
+    """) == Shader.C_standard_derivatives
+
+
+def test_glsl_caps_shadow_samplers():
+    assert compile_and_get_caps(Stage.fragment, """
+    #version 330
+
+    uniform sampler2DShadow a;
+
+    void main() {
+        gl_FragColor = vec4(texture(a, vec3(0.0, 0.0, 0.0)));
+    }
+    """) == Shader.C_shadow_samplers
+
+
+def test_glsl_caps_non_square_matrices():
+    assert compile_and_get_caps(Stage.vertex, """
+    #version 330
+
+    uniform mat3x4 a;
+
+    void main() {
+        gl_Position = a * vec3(1.0);
+    }
+    """) == Shader.C_non_square_matrices
+
+
+def test_glsl_caps_unified_model_uint_uniform():
+    assert compile_and_get_caps(Stage.vertex, """
+    #version 330
+
+    uniform uint a;
+
+    void main() {
+        gl_Position = vec4(float(a));
+    }
+    """) == Shader.C_unified_model
+
+
+def test_glsl_caps_unified_model_int_varying():
+    assert compile_and_get_caps(Stage.fragment, """
+    #version 330
+
+    flat in int a;
+
+    void main() {
+        gl_FragColor = vec4(float(a), 0.0, 0.0, 1.0);
+    }
+    """) == Shader.C_unified_model
+
+
+def test_glsl_caps_unified_model_round():
+    # DO NOT match unified model for regular round()
+    assert compile_and_get_caps(Stage.vertex, """
+    #version 330
+
+    uniform float a;
+
+    void main() {
+        gl_Position = vec4(round(a));
+    }
+    """) == 0
+
+    # DO match it for roundEven()
+    assert compile_and_get_caps(Stage.vertex, """
+    #version 330
+
+    uniform float a;
+
+    void main() {
+        gl_Position = vec4(roundEven(a));
+    }
+    """) == Shader.C_unified_model
+
+
+def test_glsl_caps_noperspective_interpolation():
+    assert compile_and_get_caps(Stage.fragment, """
+    #version 330
+
+    noperspective in vec4 a;
+
+    void main() {
+        gl_FragColor = a;
+    }
+    """) == Shader.C_noperspective_interpolation
+
+
+def test_glsl_caps_texture_array():
+    assert compile_and_get_caps(Stage.fragment, """
+    #version 330
+
+    uniform sampler2DArray a;
+
+    void main() {
+        gl_FragColor = texture(a, vec3(0.0));
+    }
+    """) == Shader.C_texture_array
+
+
+def test_glsl_caps_texture_lod():
+    assert compile_and_get_caps(Stage.fragment, """
+    #version 330
+
+    uniform sampler2D a;
+
+    void main() {
+        gl_FragColor = textureLod(a, vec2(0.0), 0);
+    }
+    """) == Shader.C_texture_lod
+
+
+def test_glsl_caps_texture_query_size():
+    assert compile_and_get_caps(Stage.fragment, """
+    #version 330
+
+    uniform sampler2D a;
+
+    void main() {
+        gl_FragColor = vec4(vec2(textureSize(a, 0)), 0.0, 1.0);
+    }
+    """) == Shader.C_texture_query_size
+
+
+def test_glsl_caps_sampler_cube_shadow():
+    assert compile_and_get_caps(Stage.fragment, """
+    #version 330
+
+    uniform samplerCubeShadow a;
+
+    void main() {
+        gl_FragColor = vec4(texture(a, vec4(0.0)), 0.0, 0.0, 1.0);
+    }
+    """) == Shader.C_shadow_samplers | Shader.C_sampler_cube_shadow
+
+
+def test_glsl_caps_vertex_id():
+    assert compile_and_get_caps(Stage.vertex, """
+    #version 330
+
+    void main() {
+        gl_Position = vec4(float(gl_VertexID), 0.0, 0.0, 1.0);
+    }
+    """) == Shader.C_vertex_id
+
+
+def test_glsl_caps_draw_buffers():
+    assert compile_and_get_caps(Stage.fragment, """
+    #version 330
+
+    layout(location=0) out vec4 a;
+    layout(location=1) out vec4 b;
+
+    void main() {
+        a = vec4(1.0, 0.0, 0.0, 1.0);
+        b = vec4(0.0, 1.0, 0.0, 1.0);
+    }
+    """) == Shader.C_draw_buffers
+
+
+def test_glsl_caps_clip_distance():
+    assert compile_and_get_caps(Stage.vertex, """
+    #version 330
+
+    void main() {
+        gl_Position = vec4(0.0);
+        gl_ClipDistance[0] = 0.0;
+    }
+    """) == Shader.C_clip_distance
+
+
+def test_glsl_caps_instance_id():
+    assert compile_and_get_caps(Stage.vertex, """
+    #version 330
+
+    void main() {
+        gl_Position = vec4(float(gl_InstanceID), 0.0, 0.0, 1.0);
+    }
+    """) == Shader.C_instance_id
+
+
+def test_glsl_caps_geometry_shader():
+    assert compile_and_get_caps(Stage.geometry, """
+    #version 330
+
+    layout(triangles) in;
+    layout(triangle_strip, max_vertices=3) out;
+
+    void main() {
+        gl_Position = gl_in[0].gl_Position;
+        EmitVertex();
+        gl_Position = gl_in[1].gl_Position;
+        EmitVertex();
+        gl_Position = gl_in[2].gl_Position;
+        EmitVertex();
+        EndPrimitive();
+    }
+    """) == Shader.C_geometry_shader
+
+
+def test_glsl_caps_primitive_id():
+    assert compile_and_get_caps(Stage.fragment, """
+    #version 330
+
+    void main() {
+        gl_FragColor = vec4(float(gl_PrimitiveID), 0.0, 0.0, 1.0);
+    }
+    """) == Shader.C_primitive_id
+
+
+def test_glsl_caps_bit_encoding():
+    assert compile_and_get_caps(Stage.vertex, """
+    #version 330
+
+    uniform uint a;
+
+    void main() {
+        gl_Position = vec4(uintBitsToFloat(a));
+    }
+    """) == Shader.C_unified_model | Shader.C_bit_encoding
+
+
+def test_glsl_caps_texture_query_lod():
+    assert compile_and_get_caps(Stage.fragment, """
+    #version 400
+
+    uniform sampler2D a;
+
+    void main() {
+        gl_FragColor = vec4(textureQueryLod(a, vec2(0.0)), 0.0, 1.0);
+    }
+    """) == Shader.C_texture_query_lod
+
+
+def test_glsl_caps_texture_gather_red():
+    assert compile_and_get_caps(Stage.fragment, """
+    #version 400
+
+    uniform sampler2D a;
+
+    void main() {
+        gl_FragColor = textureGather(a, vec2(0.0)) + textureGather(a, vec2(0.0), 0);
+    }
+    """) == Shader.C_texture_gather_red
+
+
+def test_glsl_caps_texture_gather_any():
+    assert compile_and_get_caps(Stage.fragment, """
+    #version 400
+
+    uniform sampler2D a;
+
+    void main() {
+        gl_FragColor = textureGather(a, vec2(0.0), 1);
+    }
+    """) == Shader.C_texture_gather_any
+
+
+def test_glsl_caps_extended_arithmetic():
+    assert compile_and_get_caps(Stage.fragment, """
+    #version 400
+
+    uniform uint a;
+    uniform uint b;
+
+    void main() {
+        uint c;
+        uint d = uaddCarry(a, b, c);
+        gl_FragColor = vec4(a, b, c, d);
+    }
+    """) == Shader.C_unified_model | Shader.C_extended_arithmetic
+
+
+def test_glsl_caps_double():
+    assert compile_and_get_caps(Stage.vertex, """
+    #version 400
+
+    uniform double a;
+
+    void main() {
+        gl_Position = vec4(float(a), 0.0, 0.0, 1.0);
+    }
+    """) == Shader.C_double
+
+
+def test_glsl_caps_cube_map_array():
+    assert compile_and_get_caps(Stage.fragment, """
+    #version 400
+
+    uniform samplerCubeArray a;
+
+    void main() {
+        gl_FragColor = texture(a, vec4(0.0));
+    }
+    """) == Shader.C_texture_array | Shader.C_cube_map_array
+
+
+def test_glsl_caps_geometry_shader_instancing():
+    assert compile_and_get_caps(Stage.geometry, """
+    #version 400
+
+    layout(triangles, invocations=2) in;
+    layout(triangle_strip, max_vertices=3) out;
+
+    void main() {
+        gl_Position = gl_in[0].gl_Position;
+        EmitVertex();
+        gl_Position = gl_in[1].gl_Position;
+        EmitVertex();
+        gl_Position = gl_in[2].gl_Position;
+        EmitVertex();
+        EndPrimitive();
+    }
+    """) == Shader.C_geometry_shader | Shader.C_geometry_shader_instancing
+
+
+def test_glsl_caps_tessellation_shader():
+    assert compile_and_get_caps(Stage.tess_control, """
+    #version 400 core
+
+    layout (vertices = 3) out;
+
+    void main() {
+        gl_TessLevelOuter[0] = 1;
+        gl_TessLevelOuter[1] = 1;
+        gl_TessLevelOuter[2] = 1;
+        gl_TessLevelInner[0] = 1;
+    }
+    """) == Shader.C_tessellation_shader
+
+    assert compile_and_get_caps(Stage.tess_evaluation, """
+    #version 400 core
+
+    layout(triangles, equal_spacing, ccw) in;
+
+    void main() {
+        gl_Position = vec4(0.0);
+    }
+    """) == Shader.C_tessellation_shader
+
+
+def test_glsl_caps_sample_variables():
+    assert compile_and_get_caps(Stage.fragment, """
+    #version 330
+
+    #extension GL_ARB_sample_shading : require
+
+    void main() {
+        gl_FragColor = vec4(float(gl_SampleID), 0.0, 0.0, 1.0);
+    }
+    """) == Shader.C_sample_variables
+
+
+def test_glsl_caps_multisample_interpolation():
+    assert compile_and_get_caps(Stage.fragment, """
+    #version 400
+
+    sample in vec4 a;
+
+    out vec4 p3d_FragColor;
+
+    void main() {
+        p3d_FragColor = a;
+    }
+    """) == Shader.C_multisample_interpolation
+
+
+def test_glsl_caps_atomic_counters():
+    assert compile_and_get_caps(Stage.vertex, """
+    #version 420
+
+    uniform atomic_uint a;
+
+    void main() {
+        gl_Position = vec4(float(atomicCounter(a)));
+    }
+    """) == Shader.C_unified_model | Shader.C_atomic_counters
+
+
+def test_glsl_caps_image_load_store():
+    assert compile_and_get_caps(Stage.fragment, """
+    #version 420
+
+    layout(rgba8) uniform readonly image2D a;
+
+    out vec4 p3d_FragColor;
+
+    void main() {
+        p3d_FragColor = imageLoad(a, ivec2(0, 0));
+    }
+    """) == Shader.C_image_load_store
+
+
+def test_glsl_caps_image_atomic():
+    assert compile_and_get_caps(Stage.fragment, """
+    #version 420
+
+    layout(r32i) uniform iimage1D a;
+
+    out vec4 p3d_FragColor;
+
+    void main() {
+        imageAtomicExchange(a, 0, 0);
+        p3d_FragColor = vec4(0.0);
+    }
+    """) == Shader.C_image_load_store | Shader.C_image_atomic
+
+
+def test_glsl_caps_image_query_size():
+    assert compile_and_get_caps(Stage.fragment, """
+    #version 420
+
+    #extension GL_ARB_shader_image_size : require
+
+    uniform writeonly image2D a;
+
+    out vec4 p3d_FragColor;
+
+    void main() {
+        p3d_FragColor = vec4(vec2(imageSize(a)), 0.0, 1.0);
+    }
+    """) == Shader.C_image_query_size
+
+
+def test_glsl_caps_texture_query_levels():
+    assert compile_and_get_caps(Stage.fragment, """
+    #version 430
+
+    uniform sampler2D a;
+
+    out vec4 p3d_FragColor;
+
+    void main() {
+        p3d_FragColor = vec4(float(textureQueryLevels(a)), 0.0, 0.0, 1.0);
+    }
+    """) == Shader.C_texture_query_levels
+
+
+def test_glsl_caps_texture_storage_buffer():
+    assert compile_and_get_caps(Stage.vertex, """
+    #version 430
+
+    buffer A {
+        vec4 pos;
+    } a;
+
+    void main() {
+        gl_Position = a.pos;
+    }
+    """) == Shader.C_storage_buffer
+
+    # Don't trigger for a regular UBO
+    assert compile_and_get_caps(Stage.vertex, """
+    #version 430
+
+    uniform A {
+        vec4 pos;
+    } a;
+
+    void main() {
+        gl_Position = a.pos;
+    }
+    """) == 0
+
+
+def test_glsl_caps_compute_shader():
+    assert compile_and_get_caps(Stage.compute, """
+    #version 430
+
+    layout(local_size_x=16, local_size_y=16) in;
+
+    void main() {
+    }
+    """) == Shader.C_compute_shader
+
+
+def test_glsl_caps_enhanced_layouts():
+    assert compile_and_get_caps(Stage.vertex, """
+    #version 450
+
+    layout(location=0, component=1) out float a;
+
+    void main() {
+        gl_Position = vec4(0.0);
+        a = 0.0;
+    }
+    """) == Shader.C_enhanced_layouts
+
+
+def test_glsl_caps_cull_distance():
+    assert compile_and_get_caps(Stage.vertex, """
+    #version 450
+
+    void main() {
+        gl_Position = vec4(0.0);
+        gl_CullDistance[0] = 0.0;
+    }
+    """) == Shader.C_cull_distance
+
+
+def test_glsl_caps_derivative_control():
+    assert compile_and_get_caps(Stage.fragment, """
+    #version 450
+
+    out vec4 p3d_FragColor;
+
+    void main() {
+        p3d_FragColor = vec4(fwidthFine(gl_FragCoord.x), 0.0, 0.0, 1.0);
+    }
+    """) == Shader.C_standard_derivatives | Shader.C_derivative_control
+
+
+def test_glsl_caps_texture_query_samples():
+    assert compile_and_get_caps(Stage.fragment, """
+    #version 450
+
+    uniform sampler2DMS a;
+
+    out vec4 p3d_FragColor;
+
+    void main() {
+        p3d_FragColor = vec4(float(textureSamples(a)), 0.0, 0.0, 1.0);
+    }
+    """) == Shader.C_texture_query_samples