Browse Source

shaderpipeline: fix type of FragCoord built-in to vec4

glslang doesn't check or convert this, and Vulkan complains about this, but in general FragCoord is meant to be vec4 so let's have Panda fix this in the front-end
rdb 1 year ago
parent
commit
b9ef2d050f

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

@@ -302,6 +302,30 @@ ShaderModuleSpirV(Stage stage, std::vector<uint32_t> words, BamCacheRecord *reco
         _used_caps |= C_sample_variables;
         break;
 
+      case spv::BuiltInFragCoord:
+        // glslang doesn't always check this properly, so we may need to convert
+        // this to a vec4 in order to get a valid module.
+        {
+          const ShaderType::Vector *vector_type = def._type->as_vector();
+          if (vector_type != nullptr || def._type->as_scalar() != nullptr) {
+            if (vector_type == nullptr ||
+                vector_type->get_scalar_type() != ShaderType::ST_float ||
+                vector_type->get_num_components() != 4) {
+              ShaderType::ScalarType scalar_type =
+                (vector_type != nullptr)
+                ? vector_type->get_scalar_type()
+                : def._type->as_scalar()->get_scalar_type();
+
+              const ShaderType *new_type = ShaderType::register_type(ShaderType::Vector(scalar_type, 4));
+              transformer.run(SpirVReplaceVariableTypePass(id, new_type, spv::StorageClassInput));
+            }
+          } else {
+            shader_cat.error()
+              << "FragCoord input must be a vector!\n";
+          }
+        }
+        break;
+
       default:
         break;
       }

+ 93 - 0
panda/src/shaderpipeline/spirVReplaceVariableTypePass.cxx

@@ -47,6 +47,7 @@ transform_definition_op(Instruction op) {
         (uint32_t)_new_storage_class,
       });
       def._type = _new_type;
+      def._type_id = _pointer_type_id;
       if (def.is_used()) {
         _db.mark_used(_variable_id);
       }
@@ -66,6 +67,98 @@ bool SpirVReplaceVariableTypePass::
 transform_function_op(Instruction op) {
   switch (op.opcode) {
   case spv::OpLoad:
+    if (_pointer_ids.count(op.args[2])) {
+      Definition &def = _db.modify_definition(op.args[1]);
+
+      // If both are vectors or scalars, we can try a conversion.
+      const ShaderType::Vector *old_vector = _new_type->as_vector();
+      const ShaderType::Scalar *old_scalar = _new_type->as_scalar();
+      const ShaderType::Vector *new_vector = def._type->as_vector();
+      const ShaderType::Scalar *new_scalar = def._type->as_scalar();
+      if ((old_vector != nullptr && new_vector != nullptr && old_vector != new_vector) ||
+          (old_scalar != nullptr && new_scalar != nullptr && old_scalar != new_scalar) ||
+          (old_vector != nullptr && new_scalar != nullptr) ||
+          (old_scalar != nullptr && new_vector != nullptr)) {
+        uint32_t temp = op_load(op.args[2]);
+        ShaderType::ScalarType old_scalar_type, new_scalar_type;
+        if (new_vector != nullptr && old_vector != nullptr) {
+          // Swizzle the vector.
+          old_scalar_type = old_vector->get_scalar_type();
+          new_scalar_type = new_vector->get_scalar_type();
+          if (new_vector->get_num_components() != old_vector->get_num_components()) {
+            pvector<uint32_t> components;
+            uint32_t i = 0;
+            while (i < new_vector->get_num_components() && i < old_vector->get_num_components()) {
+              components.push_back(i);
+              ++i;
+            }
+            // The remaining components are undefined.
+            while (i < new_vector->get_num_components()) {
+              components.push_back(0xffffffff);
+              ++i;
+            }
+            temp = op_vector_shuffle(temp, temp, components);
+          }
+        }
+        else if (new_vector != nullptr) {
+          // Convert scalar to vector.
+          old_scalar_type = old_scalar->get_scalar_type();
+          new_scalar_type = new_vector->get_scalar_type();
+          pvector<uint32_t> components(new_vector->get_num_components(), temp);
+          temp = op_composite_construct(new_scalar, components);
+        }
+        else if (new_scalar != nullptr) {
+          // Convert vector to scalar.
+          old_scalar_type = old_vector->get_scalar_type();
+          new_scalar_type = new_scalar->get_scalar_type();
+          temp = op_composite_extract(temp, {0});
+        }
+        else {
+          old_scalar_type = old_scalar->get_scalar_type();
+          new_scalar_type = new_scalar->get_scalar_type();
+        }
+
+        // Determine which conversion instruction to use.
+        spv::Op opcode;
+        if (old_scalar_type != new_scalar_type) {
+          bool old_float = old_scalar_type == ShaderType::ST_float
+                        || old_scalar_type == ShaderType::ST_double;
+          bool new_float = new_scalar_type == ShaderType::ST_float
+                        || new_scalar_type == ShaderType::ST_double;
+
+          if (old_float && new_float) {
+            opcode = spv::OpFConvert;
+          }
+          else if (old_float) {
+            bool new_signed = new_scalar_type == ShaderType::ST_int;
+            opcode = new_signed ? spv::OpConvertFToS : spv::OpConvertFToU;
+          }
+          else if (new_float) {
+            bool old_signed = old_scalar_type == ShaderType::ST_int;
+            opcode = old_signed ? spv::OpConvertSToF : spv::OpConvertUToF;
+          }
+          else {
+            // Assuming it's the same bit width, for now.
+            opcode = spv::OpBitcast;
+          }
+        } else {
+          // Redundant instruction, but keeps the logic here simple.
+          opcode = spv::OpCopyObject;
+        }
+        // Replace the original load with our conversion.
+        add_instruction(opcode, {op.args[0], op.args[1], temp});
+        return false;
+      }
+      else {
+        def._type = _new_type;
+        def._type_id = _type_id;
+
+        op.args[0] = _type_id;
+        _object_ids.insert(op.args[1]);
+      }
+    }
+    break;
+
   case spv::OpAtomicLoad:
   case spv::OpAtomicExchange:
   case spv::OpAtomicCompareExchange:

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

@@ -19,7 +19,9 @@
 /**
  * Changes the type of a given variable.  Does not check that the existing
  * usage of the variable in the shader is valid with the new type - it only
- * changes the types of loads and copies.
+ * changes the types of loads and copies.  An exception is when changing a
+ * scalar or vector to a different scalar type or number of components, where
+ * conversion is performed at the load point.
  */
 class EXPCL_PANDA_SHADERPIPELINE SpirVReplaceVariableTypePass final : public SpirVTransformPass {
 public:

+ 47 - 0
panda/src/shaderpipeline/spirVTransformPass.cxx

@@ -1130,6 +1130,52 @@ op_access_chain(uint32_t var_id, std::initializer_list<uint32_t> chain) {
   return id;
 }
 
+/**
+ * Inserts an OpVectorShuffle, like a swizzle but may source from two vectors
+ * at once, with the indices continuing to number into the second vector.
+ * For a regular swizzle, pass the same vector twice.
+ */
+uint32_t SpirVTransformPass::
+op_vector_shuffle(uint32_t vec1, uint32_t vec2, const pvector<uint32_t> &components) {
+  const ShaderType::Vector *vec1_type = resolve_type(get_type_id(vec1))->as_vector();
+  const ShaderType::Vector *vec2_type = resolve_type(get_type_id(vec2))->as_vector();
+  nassertr(vec1_type != nullptr && vec2_type != nullptr, 0);
+  nassertr(vec1_type->get_scalar_type() == vec2_type->get_scalar_type(), 0);
+
+  const ShaderType *result_type = ShaderType::register_type(ShaderType::Vector(vec1_type->get_scalar_type(), components.size()));
+  uint32_t type_id = define_type(result_type);
+
+  uint32_t id = allocate_id();
+  _new_functions.insert(_new_functions.end(), {((5 + (uint32_t)components.size()) << spv::WordCountShift) | spv::OpVectorShuffle, type_id, id, vec1, vec2});
+  _new_functions.insert(_new_functions.end(), components.begin(), components.end());
+
+  Definition &def = _db.modify_definition(id);
+  def._type_id = type_id;
+  def._type = result_type;
+
+  mark_defined(id);
+  return id;
+}
+
+/**
+ * Constructs a composite with the given type from the given constituents.
+ */
+uint32_t SpirVTransformPass::
+op_composite_construct(const ShaderType *type, const pvector<uint32_t> &constituents) {
+  uint32_t type_id = define_type(type);
+
+  uint32_t id = allocate_id();
+  _new_functions.insert(_new_functions.end(), {((3 + (uint32_t)constituents.size()) << spv::WordCountShift) | spv::OpCompositeConstruct, type_id, id});
+  _new_functions.insert(_new_functions.end(), constituents.begin(), constituents.end());
+
+  Definition &def = _db.modify_definition(id);
+  def._type_id = type_id;
+  def._type = type;
+
+  mark_defined(id);
+  return id;
+}
+
 /**
  * Inserts an OpCompositeExtract.
  */
@@ -1158,6 +1204,7 @@ op_composite_extract(uint32_t obj_id, std::initializer_list<uint32_t> chain) {
 
   Definition &def = _db.modify_definition(id);
   def._type_id = type_id;
+  def._type = resolve_type(type_id);
 
   mark_defined(id);
   return id;

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

@@ -125,6 +125,8 @@ protected:
   uint32_t op_load(uint32_t var_id, spv::MemoryAccessMask access = spv::MemoryAccessMaskNone);
   uint32_t op_select(uint32_t cond, uint32_t obj1, uint32_t obj2);
   uint32_t op_access_chain(uint32_t var_id, std::initializer_list<uint32_t>);
+  uint32_t op_vector_shuffle(uint32_t vec1, uint32_t vec2, const pvector<uint32_t> &components);
+  uint32_t op_composite_construct(const ShaderType *type, const pvector<uint32_t> &constituents);
   uint32_t op_composite_extract(uint32_t obj_id, std::initializer_list<uint32_t>);
 
   // The module is split into sections to make it easier to add instructions