Browse Source

shaderpipeline: Readonly/writeonly tracking, preparation for SSBOs

rdb 2 years ago
parent
commit
b931e715c9

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

@@ -196,7 +196,7 @@ get_sampled_type() const {
 /**
  * Returns the way this image is accessed.
  */
-INLINE ShaderType::Image::Access ShaderType::Image::
+INLINE ShaderType::Access ShaderType::Image::
 get_access() const {
   return _access;
 }

+ 6 - 0
panda/src/gobj/shaderType.cxx

@@ -786,6 +786,12 @@ make_from_bam(const FactoryParams &params) {
  */
 void ShaderType::Image::
 output(std::ostream &out) const {
+  if ((_access & Access::write_only) == Access::none) {
+    out << "readonly ";
+  }
+  if ((_access & Access::read_only) == Access::none) {
+    out << "writeonly ";
+  }
   if (_sampled_type == ST_int) {
     out << 'i';
   } else if (_sampled_type == ST_uint) {

+ 15 - 8
panda/src/gobj/shaderType.h

@@ -49,6 +49,13 @@ public:
     ST_bool,
   };
 
+  enum class Access {
+    none = 0,
+    read_only = 1,
+    write_only = 2,
+    read_write = 3,
+  };
+
 private:
   typedef pset<const ShaderType *, indirect_compare_to<const ShaderType *> > Registry;
   static Registry *_registered_types;
@@ -111,6 +118,14 @@ private:
   static TypeHandle _type_handle;
 };
 
+constexpr ShaderType::Access operator & (ShaderType::Access a, ShaderType::Access b) {
+  return (ShaderType::Access)((unsigned int)a & (unsigned int)b);
+}
+
+constexpr ShaderType::Access operator | (ShaderType::Access a, ShaderType::Access b) {
+  return (ShaderType::Access)((unsigned int)a | (unsigned int)b);
+}
+
 std::ostream &operator << (std::ostream &out, ShaderType::ScalarType scalar_type);
 
 INLINE std::ostream &operator << (std::ostream &out, const ShaderType &stype) {
@@ -370,14 +385,6 @@ private:
  * Image type.
  */
 class EXPCL_PANDA_GOBJ ShaderType::Image final : public ShaderType {
-PUBLISHED:
-  enum class Access {
-    unknown = 0,
-    read_only = 1,
-    write_only = 2,
-    read_write = 3,
-  };
-
 public:
   INLINE Image(Texture::TextureType texture_type, ScalarType sampled_type, Access access);
 

+ 111 - 19
panda/src/shaderpipeline/shaderModuleSpirV.cxx

@@ -234,17 +234,18 @@ ShaderModuleSpirV(Stage stage, std::vector<uint32_t> words, BamCacheRecord *reco
         _parameters.push_back(std::move(var));
       }
       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.
+        // For whatever reason, in GLSL, the name of an SSBO is derived from the
+        // name of the struct type.
         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;
-        }
+        nassertd(!type_def._name.empty()) continue;
+
+        var.name = InternalName::make(type_def._name);
+        _parameters.push_back(std::move(var));
+
+        _used_caps |= C_storage_buffer;
       }
     }
     else if (def._dtype == DT_variable && def.is_used() &&
@@ -2188,17 +2189,16 @@ r_define_type(InstructionIterator &it, const ShaderType *type) {
 
     uint32_t nargs = 8;
     switch (image_type->get_access()) {
-    case ShaderType::Image::Access::unknown:
-      break;
-    case ShaderType::Image::Access::read_only:
+    case ShaderType::Access::none:
+    case ShaderType::Access::read_only:
       args[8] = spv::AccessQualifierReadOnly;
       ++nargs;
       break;
-    case ShaderType::Image::Access::write_only:
+    case ShaderType::Access::write_only:
       args[8] = spv::AccessQualifierWriteOnly;
       ++nargs;
       break;
-    case ShaderType::Image::Access::read_write:
+    case ShaderType::Access::read_write:
       args[8] = spv::AccessQualifierReadWrite;
       ++nargs;
       break;
@@ -2518,17 +2518,17 @@ parse_instruction(const Instruction &op, uint32_t &current_function_id) {
         return;
       }
 
-      ShaderType::Image::Access access = ShaderType::Image::Access::unknown;
+      ShaderType::Access access = ShaderType::Access::read_write;
       if (op.nargs > 8) {
         switch ((spv::AccessQualifier)op.args[8]) {
         case spv::AccessQualifierReadOnly:
-          access = ShaderType::Image::Access::read_only;
+          access = ShaderType::Access::read_only;
           break;
         case spv::AccessQualifierWriteOnly:
-          access = ShaderType::Image::Access::write_only;
+          access = ShaderType::Access::write_only;
           break;
         case spv::AccessQualifierReadWrite:
-          access = ShaderType::Image::Access::read_write;
+          access = ShaderType::Access::read_write;
           break;
         default:
           shader_cat.error()
@@ -2536,6 +2536,12 @@ parse_instruction(const Instruction &op, uint32_t &current_function_id) {
           break;
         }
       }
+      if (_defs[op.args[0]]._flags & DF_non_writable) {
+        access = (access & ShaderType::Access::read_only);
+      }
+      if (_defs[op.args[0]]._flags & DF_non_readable) {
+        access = (access & ShaderType::Access::write_only);
+      }
 
       record_type(op.args[0], ShaderType::register_type(
         ShaderType::Image(texture_type, sampled_type->get_scalar_type(), access)));
@@ -2575,6 +2581,7 @@ parse_instruction(const Instruction &op, uint32_t &current_function_id) {
   case spv::OpTypeStruct:
     {
       Definition &struct_def = _defs[op.args[0]];
+      int access_flags = DF_non_writable | DF_non_readable;
       ShaderType::Struct type;
       for (size_t i = 0; i < op.nargs - 1; ++i) {
         uint32_t member_type_id = op.args[i + 1];
@@ -2591,13 +2598,42 @@ parse_instruction(const Instruction &op, uint32_t &current_function_id) {
           // Ignore built-in member.
           continue;
         }
+
+        const ShaderType *member_type = _defs[member_type_id]._type;
+        if (member_def._flags & (DF_non_writable | DF_non_readable)) {
+          // If an image member has the readonly/writeonly qualifiers,
+          // then we'll inject those back into the type.
+          if (const ShaderType::Image *image = member_type->as_image()) {
+            ShaderType::Access access = image->get_access();
+            if (member_def._flags & DF_non_writable) {
+              access = (access & ShaderType::Access::read_only);
+            }
+            if (member_def._flags & DF_non_readable) {
+              access = (access & ShaderType::Access::write_only);
+            }
+            if (access != image->get_access()) {
+              member_type = ShaderType::register_type(ShaderType::Image(
+                image->get_texture_type(),
+                image->get_sampled_type(),
+                access));
+            }
+          }
+        }
         if (member_def._offset >= 0) {
-          type.add_member(_defs[member_type_id]._type, member_def._name, (uint32_t)member_def._offset);
+          type.add_member(member_type, member_def._name, (uint32_t)member_def._offset);
         } else {
-          type.add_member(_defs[member_type_id]._type, member_def._name);
+          type.add_member(member_type, member_def._name);
         }
+
+        // If any member is writable, the struct shan't be marked readonly.
+        access_flags &= member_def._flags;
       }
       record_type(op.args[0], ShaderType::register_type(std::move(type)));
+
+      // If all struct members are flagged readonly/writeonly, we tag the type
+      // so as well, since glslang doesn't decorate an SSBO in its entirety as
+      // readonly/writeonly properly (it applies it to all members instead)
+      _defs[op.args[0]]._flags |= access_flags;
     }
     break;
 
@@ -2780,6 +2816,14 @@ parse_instruction(const Instruction &op, uint32_t &current_function_id) {
       _defs[op.args[0]]._builtin = (spv::BuiltIn)op.args[2];
       break;
 
+    case spv::DecorationNonWritable:
+      _defs[op.args[0]]._flags |= DF_non_writable;
+      break;
+
+    case spv::DecorationNonReadable:
+      _defs[op.args[0]]._flags |= DF_non_readable;
+      break;
+
     case spv::DecorationLocation:
       _defs[op.args[0]]._location = op.args[2];
       break;
@@ -2803,8 +2847,30 @@ parse_instruction(const Instruction &op, uint32_t &current_function_id) {
       _defs[op.args[0]].modify_member(op.args[1])._builtin = (spv::BuiltIn)op.args[3];
       break;
 
+    case spv::DecorationNonWritable:
+      _defs[op.args[0]].modify_member(op.args[1])._flags |= DF_non_writable;
+      break;
+
+    case spv::DecorationNonReadable:
+      _defs[op.args[0]].modify_member(op.args[1])._flags |= DF_non_readable;
+      break;
+
+    case spv::DecorationLocation:
+      _defs[op.args[0]].modify_member(op.args[1])._location = op.args[3];
+      break;
+
+    case spv::DecorationBinding:
+      shader_cat.error()
+        << "Invalid " << "binding" << " decoration on struct member\n";
+      break;
+
+    case spv::DecorationDescriptorSet:
+      shader_cat.error()
+        << "Invalid " << "set" << " decoration on struct member\n";
+      break;
+
     case spv::DecorationOffset:
-      _defs[op.args[0]].modify_member(op.args[1])._offset = (spv::BuiltIn)op.args[3];
+      _defs[op.args[0]].modify_member(op.args[1])._offset = op.args[3];
       break;
 
     default:
@@ -3037,6 +3103,12 @@ record_variable(uint32_t id, uint32_t type_pointer_id, spv::StorageClass storage
     return;
   }
 
+  // In older versions of SPIR-V, an SSBO was defined using BufferBlock.
+  if (storage_class == spv::StorageClassUniform &&
+      (type_def._flags & DF_buffer_block) != 0) {
+    storage_class = spv::StorageClassStorageBuffer;
+  }
+
   def._dtype = DT_variable;
   def._type = type_def._type;
   def._type_id = type_pointer_id;
@@ -3044,6 +3116,26 @@ record_variable(uint32_t id, uint32_t type_pointer_id, spv::StorageClass storage
   def._origin_id = id;
   def._function_id = function_id;
 
+  if (def._flags & (DF_non_writable | DF_non_readable)) {
+    // If an image variable has the readonly/writeonly qualifiers, then we'll
+    // inject those back into the type.
+    if (const ShaderType::Image *image = def._type->as_image()) {
+      ShaderType::Access access = image->get_access();
+      if (def._flags & DF_non_writable) {
+        access = (access & ShaderType::Access::read_only);
+      }
+      if (def._flags & DF_non_readable) {
+        access = (access & ShaderType::Access::write_only);
+      }
+      if (access != image->get_access()) {
+        def._type = ShaderType::register_type(ShaderType::Image(
+          image->get_texture_type(),
+          image->get_sampled_type(),
+          access));
+      }
+    }
+  }
+
 #ifndef NDEBUG
   if (storage_class == spv::StorageClassUniformConstant && shader_cat.is_debug()) {
     std::ostream &out = shader_cat.debug()

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

@@ -150,6 +150,10 @@ public:
 
     // Has the "buffer block" decoration (older versions of SPIR-V).
     DF_buffer_block = 64,
+
+    // If both of these are set, no access is permitted (size queries only)
+    DF_non_writable = 128, // readonly
+    DF_non_readable = 256, // writeonly
   };
 
   /**
@@ -161,6 +165,7 @@ public:
     int _location = -1;
     int _offset = -1;
     spv::BuiltIn _builtin = spv::BuiltInMax;
+    int _flags = 0; // Only readonly/writeonly
   };
   typedef pvector<MemberDefinition> MemberDefinitions;
 
@@ -183,7 +188,7 @@ public:
     MemberDefinitions _members;
     int _flags = 0;
 
-    // Only defined for DT_global and DT_type_pointer.
+    // Only defined for DT_variable and DT_type_pointer.
     spv::StorageClass _storage_class;
 
     INLINE bool is_used() const;