glsl: Avoid extra if statements for logic and/or with no side effects.
[mesa.git] / src / glsl / linker.cpp
index ae583a3d67249b024c4aa2a61743145e4d4941c9..82bddb003c3fb0077f63da91b6e2b8a249210a23 100644 (file)
@@ -246,7 +246,8 @@ count_attribute_slots(const glsl_type *t)
 /**
  * Verify that a vertex shader executable meets all semantic requirements.
  *
- * Also sets prog->Vert.UsesClipDistance as a side effect.
+ * Also sets prog->Vert.UsesClipDistance and prog->Vert.ClipDistanceArraySize
+ * as a side effect.
  *
  * \param shader  Vertex shader executable to be verified
  */
@@ -264,6 +265,8 @@ validate_vertex_shader_executable(struct gl_shader_program *prog,
       return false;
    }
 
+   prog->Vert.ClipDistanceArraySize = 0;
+
    if (prog->Version >= 130) {
       /* From section 7.1 (Vertex Shader Special Variables) of the
        * GLSL 1.30 spec:
@@ -282,6 +285,10 @@ validate_vertex_shader_executable(struct gl_shader_program *prog,
          return false;
       }
       prog->Vert.UsesClipDistance = clip_distance.variable_found();
+      ir_variable *clip_distance_var =
+         shader->symbols->get_variable("gl_ClipDistance");
+      if (clip_distance_var)
+         prog->Vert.ClipDistanceArraySize = clip_distance_var->type->length;
    }
 
    return true;
@@ -848,6 +855,27 @@ get_main_function_signature(gl_shader *sh)
 }
 
 
+/**
+ * This class is only used in link_intrastage_shaders() below but declaring
+ * it inside that function leads to compiler warnings with some versions of
+ * gcc.
+ */
+class array_sizing_visitor : public ir_hierarchical_visitor {
+public:
+   virtual ir_visitor_status visit(ir_variable *var)
+   {
+      if (var->type->is_array() && (var->type->length == 0)) {
+         const glsl_type *type =
+            glsl_type::get_array_instance(var->type->fields.array,
+                                          var->max_array_access + 1);
+         assert(type != NULL);
+         var->type = type;
+      }
+      return visit_continue;
+   }
+};
+
+
 /**
  * Combine a group of shaders for a single stage to generate a linked shader
  *
@@ -998,22 +1026,7 @@ link_intrastage_shaders(void *mem_ctx,
     * max_array_access field.
     */
    if (linked != NULL) {
-      class array_sizing_visitor : public ir_hierarchical_visitor {
-      public:
-        virtual ir_visitor_status visit(ir_variable *var)
-        {
-           if (var->type->is_array() && (var->type->length == 0)) {
-              const glsl_type *type =
-                 glsl_type::get_array_instance(var->type->fields.array,
-                                               var->max_array_access + 1);
-
-              assert(type != NULL);
-              var->type = type;
-           }
-
-           return visit_continue;
-        }
-      } v;
+      array_sizing_visitor v;
 
       v.run(linked->ir);
    }
@@ -1021,13 +1034,6 @@ link_intrastage_shaders(void *mem_ctx,
    return linked;
 }
 
-
-struct uniform_node {
-   exec_node link;
-   struct gl_uniform *u;
-   unsigned slots;
-};
-
 /**
  * Update the sizes of linked shader uniform arrays to the maximum
  * array index used.
@@ -1100,151 +1106,6 @@ update_array_sizes(struct gl_shader_program *prog)
    }
 }
 
-static void
-add_uniform(void *mem_ctx, exec_list *uniforms, struct hash_table *ht,
-           const char *name, const glsl_type *type, GLenum shader_type,
-           unsigned *next_shader_pos, unsigned *total_uniforms)
-{
-   if (type->is_record()) {
-      for (unsigned int i = 0; i < type->length; i++) {
-        const glsl_type *field_type = type->fields.structure[i].type;
-        char *field_name = ralloc_asprintf(mem_ctx, "%s.%s", name,
-                                           type->fields.structure[i].name);
-
-        add_uniform(mem_ctx, uniforms, ht, field_name, field_type,
-                    shader_type, next_shader_pos, total_uniforms);
-      }
-   } else {
-      uniform_node *n = (uniform_node *) hash_table_find(ht, name);
-      unsigned int vec4_slots;
-      const glsl_type *array_elem_type = NULL;
-
-      if (type->is_array()) {
-        array_elem_type = type->fields.array;
-        /* Array of structures. */
-        if (array_elem_type->is_record()) {
-           for (unsigned int i = 0; i < type->length; i++) {
-              char *elem_name = ralloc_asprintf(mem_ctx, "%s[%d]", name, i);
-              add_uniform(mem_ctx, uniforms, ht, elem_name, array_elem_type,
-                          shader_type, next_shader_pos, total_uniforms);
-           }
-           return;
-        }
-      }
-
-      /* Fix the storage size of samplers at 1 vec4 each. Be sure to pad out
-       * vectors to vec4 slots.
-       */
-      if (type->is_array()) {
-        if (array_elem_type->is_sampler())
-           vec4_slots = type->length;
-        else
-           vec4_slots = type->length * array_elem_type->matrix_columns;
-      } else if (type->is_sampler()) {
-        vec4_slots = 1;
-      } else {
-        vec4_slots = type->matrix_columns;
-      }
-
-      if (n == NULL) {
-        n = (uniform_node *) calloc(1, sizeof(struct uniform_node));
-        n->u = (gl_uniform *) calloc(1, sizeof(struct gl_uniform));
-        n->slots = vec4_slots;
-
-        n->u->Name = strdup(name);
-        n->u->Type = type;
-        n->u->VertPos = -1;
-        n->u->FragPos = -1;
-        n->u->GeomPos = -1;
-        (*total_uniforms)++;
-
-        hash_table_insert(ht, n, name);
-        uniforms->push_tail(& n->link);
-      }
-
-      switch (shader_type) {
-      case GL_VERTEX_SHADER:
-        n->u->VertPos = *next_shader_pos;
-        break;
-      case GL_FRAGMENT_SHADER:
-        n->u->FragPos = *next_shader_pos;
-        break;
-      case GL_GEOMETRY_SHADER:
-        n->u->GeomPos = *next_shader_pos;
-        break;
-      }
-
-      (*next_shader_pos) += vec4_slots;
-   }
-}
-
-void
-assign_uniform_locations(struct gl_shader_program *prog)
-{
-   /* */
-   exec_list uniforms;
-   unsigned total_uniforms = 0;
-   hash_table *ht = hash_table_ctor(32, hash_table_string_hash,
-                                   hash_table_string_compare);
-   void *mem_ctx = ralloc_context(NULL);
-
-   for (unsigned i = 0; i < MESA_SHADER_TYPES; i++) {
-      if (prog->_LinkedShaders[i] == NULL)
-        continue;
-
-      unsigned next_position = 0;
-
-      foreach_list(node, prog->_LinkedShaders[i]->ir) {
-        ir_variable *const var = ((ir_instruction *) node)->as_variable();
-
-        if ((var == NULL) || (var->mode != ir_var_uniform))
-           continue;
-
-        if (strncmp(var->name, "gl_", 3) == 0) {
-           /* At the moment, we don't allocate uniform locations for
-            * builtin uniforms.  It's permitted by spec, and we'll
-            * likely switch to doing that at some point, but not yet.
-            */
-           continue;
-        }
-
-        var->location = next_position;
-        add_uniform(mem_ctx, &uniforms, ht, var->name, var->type,
-                    prog->_LinkedShaders[i]->Type,
-                    &next_position, &total_uniforms);
-      }
-   }
-
-   ralloc_free(mem_ctx);
-
-   gl_uniform_list *ul = (gl_uniform_list *)
-      calloc(1, sizeof(gl_uniform_list));
-
-   ul->Size = total_uniforms;
-   ul->NumUniforms = total_uniforms;
-   ul->Uniforms = (gl_uniform *) calloc(total_uniforms, sizeof(gl_uniform));
-
-   unsigned idx = 0;
-   uniform_node *next;
-   for (uniform_node *node = (uniform_node *) uniforms.head
-          ; node->link.next != NULL
-          ; node = next) {
-      next = (uniform_node *) node->link.next;
-
-      node->link.remove();
-      memcpy(&ul->Uniforms[idx], node->u, sizeof(gl_uniform));
-      idx++;
-
-      free(node->u);
-      free(node);
-   }
-
-   hash_table_dtor(ht);
-
-   prog->Uniforms = ul;
-}
-
-
 /**
  * Find a contiguous set of available bits in a bitmask.
  *
@@ -1291,12 +1152,6 @@ find_available_slots(unsigned used_mask, unsigned needed_count)
  * \return
  * If locations are successfully assigned, true is returned.  Otherwise an
  * error is emitted to the shader link log and false is returned.
- *
- * \bug
- * Locations set via \c glBindFragDataLocation are not currently supported.
- * Only locations assigned automatically by the linker, explicitly set by a
- * layout qualifier, or explicitly set by a built-in variable (e.g., \c
- * gl_FragColor) are supported for fragment shaders.
  */
 bool
 assign_attribute_or_color_locations(gl_shader_program *prog,
@@ -1320,7 +1175,8 @@ assign_attribute_or_color_locations(gl_shader_program *prog,
     * 1. Invalidate the location assignments for all vertex shader inputs.
     *
     * 2. Assign locations for inputs that have user-defined (via
-    *    glBindVertexAttribLocation) locations.
+    *    glBindVertexAttribLocation) locations and outputs that have
+    *    user-defined locations (via glBindFragDataLocation).
     *
     * 3. Sort the attributes without assigned locations by number of slots
     *    required in decreasing order.  Fragmentation caused by attribute
@@ -1381,6 +1237,13 @@ assign_attribute_or_color_locations(gl_shader_program *prog,
            assert(binding >= VERT_ATTRIB_GENERIC0);
            var->location = binding;
         }
+      } else if (target_index == MESA_SHADER_FRAGMENT) {
+        unsigned binding;
+
+        if (prog->FragDataBindings->get(binding, var->name)) {
+           assert(binding >= FRAG_RESULT_DATA0);
+           var->location = binding;
+        }
       }
 
       /* If the variable is not a built-in and has a location statically
@@ -1519,10 +1382,466 @@ demote_shader_inputs_and_outputs(gl_shader *sh, enum ir_variable_mode mode)
 }
 
 
+/**
+ * Data structure tracking information about a transform feedback declaration
+ * during linking.
+ */
+class tfeedback_decl
+{
+public:
+   bool init(struct gl_context *ctx, struct gl_shader_program *prog,
+             const void *mem_ctx, const char *input);
+   static bool is_same(const tfeedback_decl &x, const tfeedback_decl &y);
+   bool assign_location(struct gl_context *ctx, struct gl_shader_program *prog,
+                        ir_variable *output_var);
+   bool accumulate_num_outputs(struct gl_shader_program *prog, unsigned *count);
+   bool store(struct gl_context *ctx, struct gl_shader_program *prog,
+              struct gl_transform_feedback_info *info, unsigned buffer,
+              unsigned varying, const unsigned max_outputs) const;
+
+
+   /**
+    * True if assign_location() has been called for this object.
+    */
+   bool is_assigned() const
+   {
+      return this->location != -1;
+   }
+
+   /**
+    * Determine whether this object refers to the variable var.
+    */
+   bool matches_var(ir_variable *var) const
+   {
+      if (this->is_clip_distance_mesa)
+         return strcmp(var->name, "gl_ClipDistanceMESA") == 0;
+      else
+         return strcmp(var->name, this->var_name) == 0;
+   }
+
+   /**
+    * The total number of varying components taken up by this variable.  Only
+    * valid if is_assigned() is true.
+    */
+   unsigned num_components() const
+   {
+      if (this->is_clip_distance_mesa)
+         return this->size;
+      else
+         return this->vector_elements * this->matrix_columns * this->size;
+   }
+
+private:
+   /**
+    * The name that was supplied to glTransformFeedbackVaryings.  Used for
+    * error reporting and glGetTransformFeedbackVarying().
+    */
+   const char *orig_name;
+
+   /**
+    * The name of the variable, parsed from orig_name.
+    */
+   const char *var_name;
+
+   /**
+    * True if the declaration in orig_name represents an array.
+    */
+   bool is_subscripted;
+
+   /**
+    * If is_subscripted is true, the subscript that was specified in orig_name.
+    */
+   unsigned array_subscript;
+
+   /**
+    * True if the variable is gl_ClipDistance and the driver lowers
+    * gl_ClipDistance to gl_ClipDistanceMESA.
+    */
+   bool is_clip_distance_mesa;
+
+   /**
+    * The vertex shader output location that the linker assigned for this
+    * variable.  -1 if a location hasn't been assigned yet.
+    */
+   int location;
+
+   /**
+    * If location != -1, the number of vector elements in this variable, or 1
+    * if this variable is a scalar.
+    */
+   unsigned vector_elements;
+
+   /**
+    * If location != -1, the number of matrix columns in this variable, or 1
+    * if this variable is not a matrix.
+    */
+   unsigned matrix_columns;
+
+   /** Type of the varying returned by glGetTransformFeedbackVarying() */
+   GLenum type;
+
+   /**
+    * If location != -1, the size that should be returned by
+    * glGetTransformFeedbackVarying().
+    */
+   unsigned size;
+};
+
+
+/**
+ * Initialize this object based on a string that was passed to
+ * glTransformFeedbackVaryings.  If there is a parse error, the error is
+ * reported using linker_error(), and false is returned.
+ */
+bool
+tfeedback_decl::init(struct gl_context *ctx, struct gl_shader_program *prog,
+                     const void *mem_ctx, const char *input)
+{
+   /* We don't have to be pedantic about what is a valid GLSL variable name,
+    * because any variable with an invalid name can't exist in the IR anyway.
+    */
+
+   this->location = -1;
+   this->orig_name = input;
+   this->is_clip_distance_mesa = false;
+
+   const char *bracket = strrchr(input, '[');
+
+   if (bracket) {
+      this->var_name = ralloc_strndup(mem_ctx, input, bracket - input);
+      if (sscanf(bracket, "[%u]", &this->array_subscript) != 1) {
+         linker_error(prog, "Cannot parse transform feedback varying %s", input);
+         return false;
+      }
+      this->is_subscripted = true;
+   } else {
+      this->var_name = ralloc_strdup(mem_ctx, input);
+      this->is_subscripted = false;
+   }
+
+   /* For drivers that lower gl_ClipDistance to gl_ClipDistanceMESA, this
+    * class must behave specially to account for the fact that gl_ClipDistance
+    * is converted from a float[8] to a vec4[2].
+    */
+   if (ctx->ShaderCompilerOptions[MESA_SHADER_VERTEX].LowerClipDistance &&
+       strcmp(this->var_name, "gl_ClipDistance") == 0) {
+      this->is_clip_distance_mesa = true;
+   }
+
+   return true;
+}
+
+
+/**
+ * Determine whether two tfeedback_decl objects refer to the same variable and
+ * array index (if applicable).
+ */
+bool
+tfeedback_decl::is_same(const tfeedback_decl &x, const tfeedback_decl &y)
+{
+   if (strcmp(x.var_name, y.var_name) != 0)
+      return false;
+   if (x.is_subscripted != y.is_subscripted)
+      return false;
+   if (x.is_subscripted && x.array_subscript != y.array_subscript)
+      return false;
+   return true;
+}
+
+
+/**
+ * Assign a location for this tfeedback_decl object based on the location
+ * assignment in output_var.
+ *
+ * If an error occurs, the error is reported through linker_error() and false
+ * is returned.
+ */
+bool
+tfeedback_decl::assign_location(struct gl_context *ctx,
+                                struct gl_shader_program *prog,
+                                ir_variable *output_var)
+{
+   if (output_var->type->is_array()) {
+      /* Array variable */
+      const unsigned matrix_cols =
+         output_var->type->fields.array->matrix_columns;
+      unsigned actual_array_size = this->is_clip_distance_mesa ?
+         prog->Vert.ClipDistanceArraySize : output_var->type->array_size();
+
+      if (this->is_subscripted) {
+         /* Check array bounds. */
+         if (this->array_subscript >= actual_array_size) {
+            linker_error(prog, "Transform feedback varying %s has index "
+                         "%i, but the array size is %u.",
+                         this->orig_name, this->array_subscript,
+                         actual_array_size);
+            return false;
+         }
+         if (this->is_clip_distance_mesa) {
+            this->location =
+               output_var->location + this->array_subscript / 4;
+         } else {
+            this->location =
+               output_var->location + this->array_subscript * matrix_cols;
+         }
+         this->size = 1;
+      } else {
+         this->location = output_var->location;
+         this->size = actual_array_size;
+      }
+      this->vector_elements = output_var->type->fields.array->vector_elements;
+      this->matrix_columns = matrix_cols;
+      if (this->is_clip_distance_mesa)
+         this->type = GL_FLOAT;
+      else
+         this->type = output_var->type->fields.array->gl_type;
+   } else {
+      /* Regular variable (scalar, vector, or matrix) */
+      if (this->is_subscripted) {
+         linker_error(prog, "Transform feedback varying %s requested, "
+                      "but %s is not an array.",
+                      this->orig_name, this->var_name);
+         return false;
+      }
+      this->location = output_var->location;
+      this->size = 1;
+      this->vector_elements = output_var->type->vector_elements;
+      this->matrix_columns = output_var->type->matrix_columns;
+      this->type = output_var->type->gl_type;
+   }
+
+   /* From GL_EXT_transform_feedback:
+    *   A program will fail to link if:
+    *
+    *   * the total number of components to capture in any varying
+    *     variable in <varyings> is greater than the constant
+    *     MAX_TRANSFORM_FEEDBACK_SEPARATE_COMPONENTS_EXT and the
+    *     buffer mode is SEPARATE_ATTRIBS_EXT;
+    */
+   if (prog->TransformFeedback.BufferMode == GL_SEPARATE_ATTRIBS &&
+       this->num_components() >
+       ctx->Const.MaxTransformFeedbackSeparateComponents) {
+      linker_error(prog, "Transform feedback varying %s exceeds "
+                   "MAX_TRANSFORM_FEEDBACK_SEPARATE_COMPONENTS.",
+                   this->orig_name);
+      return false;
+   }
+
+   return true;
+}
+
+
+bool
+tfeedback_decl::accumulate_num_outputs(struct gl_shader_program *prog,
+                                       unsigned *count)
+{
+   if (!this->is_assigned()) {
+      /* From GL_EXT_transform_feedback:
+       *   A program will fail to link if:
+       *
+       *   * any variable name specified in the <varyings> array is not
+       *     declared as an output in the geometry shader (if present) or
+       *     the vertex shader (if no geometry shader is present);
+       */
+      linker_error(prog, "Transform feedback varying %s undeclared.",
+                   this->orig_name);
+      return false;
+   }
+
+   unsigned translated_size = this->size;
+   if (this->is_clip_distance_mesa)
+      translated_size = (translated_size + 3) / 4;
+
+   *count += translated_size * this->matrix_columns;
+
+   return true;
+}
+
+
+/**
+ * Update gl_transform_feedback_info to reflect this tfeedback_decl.
+ *
+ * If an error occurs, the error is reported through linker_error() and false
+ * is returned.
+ */
+bool
+tfeedback_decl::store(struct gl_context *ctx, struct gl_shader_program *prog,
+                      struct gl_transform_feedback_info *info,
+                      unsigned buffer,
+                      unsigned varying, const unsigned max_outputs) const
+{
+   /* From GL_EXT_transform_feedback:
+    *   A program will fail to link if:
+    *
+    *     * the total number of components to capture is greater than
+    *       the constant MAX_TRANSFORM_FEEDBACK_INTERLEAVED_COMPONENTS_EXT
+    *       and the buffer mode is INTERLEAVED_ATTRIBS_EXT.
+    */
+   if (prog->TransformFeedback.BufferMode == GL_INTERLEAVED_ATTRIBS &&
+       info->BufferStride[buffer] + this->num_components() >
+       ctx->Const.MaxTransformFeedbackInterleavedComponents) {
+      linker_error(prog, "The MAX_TRANSFORM_FEEDBACK_INTERLEAVED_COMPONENTS "
+                   "limit has been exceeded.");
+      return false;
+   }
+
+   unsigned translated_size = this->size;
+   if (this->is_clip_distance_mesa)
+      translated_size = (translated_size + 3) / 4;
+   unsigned components_so_far = 0;
+   for (unsigned index = 0; index < translated_size; ++index) {
+      for (unsigned v = 0; v < this->matrix_columns; ++v) {
+         unsigned num_components = this->vector_elements;
+         assert(info->NumOutputs < max_outputs);
+         info->Outputs[info->NumOutputs].ComponentOffset = 0;
+         if (this->is_clip_distance_mesa) {
+            if (this->is_subscripted) {
+               num_components = 1;
+               info->Outputs[info->NumOutputs].ComponentOffset =
+                  this->array_subscript % 4;
+            } else {
+               num_components = MIN2(4, this->size - components_so_far);
+            }
+         }
+         info->Outputs[info->NumOutputs].OutputRegister =
+            this->location + v + index * this->matrix_columns;
+         info->Outputs[info->NumOutputs].NumComponents = num_components;
+         info->Outputs[info->NumOutputs].OutputBuffer = buffer;
+         info->Outputs[info->NumOutputs].DstOffset = info->BufferStride[buffer];
+         ++info->NumOutputs;
+         info->BufferStride[buffer] += num_components;
+         components_so_far += num_components;
+      }
+   }
+   assert(components_so_far == this->num_components());
+
+   info->Varyings[varying].Name = ralloc_strdup(prog, this->orig_name);
+   info->Varyings[varying].Type = this->type;
+   info->Varyings[varying].Size = this->size;
+   info->NumVarying++;
+
+   return true;
+}
+
+
+/**
+ * Parse all the transform feedback declarations that were passed to
+ * glTransformFeedbackVaryings() and store them in tfeedback_decl objects.
+ *
+ * If an error occurs, the error is reported through linker_error() and false
+ * is returned.
+ */
+static bool
+parse_tfeedback_decls(struct gl_context *ctx, struct gl_shader_program *prog,
+                      const void *mem_ctx, unsigned num_names,
+                      char **varying_names, tfeedback_decl *decls)
+{
+   for (unsigned i = 0; i < num_names; ++i) {
+      if (!decls[i].init(ctx, prog, mem_ctx, varying_names[i]))
+         return false;
+      /* From GL_EXT_transform_feedback:
+       *   A program will fail to link if:
+       *
+       *   * any two entries in the <varyings> array specify the same varying
+       *     variable;
+       *
+       * We interpret this to mean "any two entries in the <varyings> array
+       * specify the same varying variable and array index", since transform
+       * feedback of arrays would be useless otherwise.
+       */
+      for (unsigned j = 0; j < i; ++j) {
+         if (tfeedback_decl::is_same(decls[i], decls[j])) {
+            linker_error(prog, "Transform feedback varying %s specified "
+                         "more than once.", varying_names[i]);
+            return false;
+         }
+      }
+   }
+   return true;
+}
+
+
+/**
+ * Assign a location for a variable that is produced in one pipeline stage
+ * (the "producer") and consumed in the next stage (the "consumer").
+ *
+ * \param input_var is the input variable declaration in the consumer.
+ *
+ * \param output_var is the output variable declaration in the producer.
+ *
+ * \param input_index is the counter that keeps track of assigned input
+ *        locations in the consumer.
+ *
+ * \param output_index is the counter that keeps track of assigned output
+ *        locations in the producer.
+ *
+ * It is permissible for \c input_var to be NULL (this happens if a variable
+ * is output by the producer and consumed by transform feedback, but not
+ * consumed by the consumer).
+ *
+ * If the variable has already been assigned a location, this function has no
+ * effect.
+ */
+void
+assign_varying_location(ir_variable *input_var, ir_variable *output_var,
+                        unsigned *input_index, unsigned *output_index)
+{
+   if (output_var->location != -1) {
+      /* Location already assigned. */
+      return;
+   }
+
+   if (input_var) {
+      assert(input_var->location == -1);
+      input_var->location = *input_index;
+   }
+
+   output_var->location = *output_index;
+
+   /* FINISHME: Support for "varying" records in GLSL 1.50. */
+   assert(!output_var->type->is_record());
+
+   if (output_var->type->is_array()) {
+      const unsigned slots = output_var->type->length
+         * output_var->type->fields.array->matrix_columns;
+
+      *output_index += slots;
+      *input_index += slots;
+   } else {
+      const unsigned slots = output_var->type->matrix_columns;
+
+      *output_index += slots;
+      *input_index += slots;
+   }
+}
+
+
+/**
+ * Assign locations for all variables that are produced in one pipeline stage
+ * (the "producer") and consumed in the next stage (the "consumer").
+ *
+ * Variables produced by the producer may also be consumed by transform
+ * feedback.
+ *
+ * \param num_tfeedback_decls is the number of declarations indicating
+ *        variables that may be consumed by transform feedback.
+ *
+ * \param tfeedback_decls is a pointer to an array of tfeedback_decl objects
+ *        representing the result of parsing the strings passed to
+ *        glTransformFeedbackVaryings().  assign_location() will be called for
+ *        each of these objects that matches one of the outputs of the
+ *        producer.
+ *
+ * When num_tfeedback_decls is nonzero, it is permissible for the consumer to
+ * be NULL.  In this case, varying locations are assigned solely based on the
+ * requirements of transform feedback.
+ */
 bool
 assign_varying_locations(struct gl_context *ctx,
                         struct gl_shader_program *prog,
-                        gl_shader *producer, gl_shader *consumer)
+                        gl_shader *producer, gl_shader *consumer,
+                         unsigned num_tfeedback_decls,
+                         tfeedback_decl *tfeedback_decls)
 {
    /* FINISHME: Set dynamically when geometry shader support is added. */
    unsigned output_index = VERT_RESULT_VAR0;
@@ -1540,96 +1859,110 @@ assign_varying_locations(struct gl_context *ctx,
     */
 
    link_invalidate_variable_locations(producer, ir_var_out, VERT_RESULT_VAR0);
-   link_invalidate_variable_locations(consumer, ir_var_in, FRAG_ATTRIB_VAR0);
+   if (consumer)
+      link_invalidate_variable_locations(consumer, ir_var_in, FRAG_ATTRIB_VAR0);
 
    foreach_list(node, producer->ir) {
       ir_variable *const output_var = ((ir_instruction *) node)->as_variable();
 
-      if ((output_var == NULL) || (output_var->mode != ir_var_out)
-         || (output_var->location != -1))
+      if ((output_var == NULL) || (output_var->mode != ir_var_out))
         continue;
 
-      ir_variable *const input_var =
-        consumer->symbols->get_variable(output_var->name);
+      ir_variable *input_var =
+        consumer ? consumer->symbols->get_variable(output_var->name) : NULL;
 
-      if ((input_var == NULL) || (input_var->mode != ir_var_in))
-        continue;
+      if (input_var && input_var->mode != ir_var_in)
+         input_var = NULL;
 
-      assert(input_var->location == -1);
-
-      output_var->location = output_index;
-      input_var->location = input_index;
-
-      /* FINISHME: Support for "varying" records in GLSL 1.50. */
-      assert(!output_var->type->is_record());
-
-      if (output_var->type->is_array()) {
-        const unsigned slots = output_var->type->length
-           * output_var->type->fields.array->matrix_columns;
-
-        output_index += slots;
-        input_index += slots;
-      } else {
-        const unsigned slots = output_var->type->matrix_columns;
+      if (input_var) {
+         assign_varying_location(input_var, output_var, &input_index,
+                                 &output_index);
+      }
 
-        output_index += slots;
-        input_index += slots;
+      for (unsigned i = 0; i < num_tfeedback_decls; ++i) {
+         if (!tfeedback_decls[i].is_assigned() &&
+             tfeedback_decls[i].matches_var(output_var)) {
+            if (output_var->location == -1) {
+               assign_varying_location(input_var, output_var, &input_index,
+                                       &output_index);
+            }
+            if (!tfeedback_decls[i].assign_location(ctx, prog, output_var))
+               return false;
+         }
       }
    }
 
    unsigned varying_vectors = 0;
 
-   foreach_list(node, consumer->ir) {
-      ir_variable *const var = ((ir_instruction *) node)->as_variable();
-
-      if ((var == NULL) || (var->mode != ir_var_in))
-        continue;
-
-      if (var->location == -1) {
-        if (prog->Version <= 120) {
-           /* On page 25 (page 31 of the PDF) of the GLSL 1.20 spec:
-            *
-            *     Only those varying variables used (i.e. read) in
-            *     the fragment shader executable must be written to
-            *     by the vertex shader executable; declaring
-            *     superfluous varying variables in a vertex shader is
-            *     permissible.
-            *
-            * We interpret this text as meaning that the VS must
-            * write the variable for the FS to read it.  See
-            * "glsl1-varying read but not written" in piglit.
-            */
-
-           linker_error(prog, "fragment shader varying %s not written "
-                        "by vertex shader\n.", var->name);
-        }
+   if (consumer) {
+      foreach_list(node, consumer->ir) {
+         ir_variable *const var = ((ir_instruction *) node)->as_variable();
+
+         if ((var == NULL) || (var->mode != ir_var_in))
+            continue;
+
+         if (var->location == -1) {
+            if (prog->Version <= 120) {
+               /* On page 25 (page 31 of the PDF) of the GLSL 1.20 spec:
+                *
+                *     Only those varying variables used (i.e. read) in
+                *     the fragment shader executable must be written to
+                *     by the vertex shader executable; declaring
+                *     superfluous varying variables in a vertex shader is
+                *     permissible.
+                *
+                * We interpret this text as meaning that the VS must
+                * write the variable for the FS to read it.  See
+                * "glsl1-varying read but not written" in piglit.
+                */
+
+               linker_error(prog, "fragment shader varying %s not written "
+                            "by vertex shader\n.", var->name);
+            }
 
-        /* An 'in' variable is only really a shader input if its
-         * value is written by the previous stage.
-         */
-        var->mode = ir_var_auto;
-      } else {
-        /* The packing rules are used for vertex shader inputs are also used
-         * for fragment shader inputs.
-         */
-        varying_vectors += count_attribute_slots(var->type);
+            /* An 'in' variable is only really a shader input if its
+             * value is written by the previous stage.
+             */
+            var->mode = ir_var_auto;
+         } else {
+            /* The packing rules are used for vertex shader inputs are also
+             * used for fragment shader inputs.
+             */
+            varying_vectors += count_attribute_slots(var->type);
+         }
       }
    }
 
    if (ctx->API == API_OPENGLES2 || prog->Version == 100) {
       if (varying_vectors > ctx->Const.MaxVarying) {
-        linker_error(prog, "shader uses too many varying vectors "
-                     "(%u > %u)\n",
-                     varying_vectors, ctx->Const.MaxVarying);
-        return false;
+         if (ctx->Const.GLSLSkipStrictMaxVaryingLimitCheck) {
+            linker_warning(prog, "shader uses too many varying vectors "
+                           "(%u > %u), but the driver will try to optimize "
+                           "them out; this is non-portable out-of-spec "
+                           "behavior\n",
+                           varying_vectors, ctx->Const.MaxVarying);
+         } else {
+            linker_error(prog, "shader uses too many varying vectors "
+                         "(%u > %u)\n",
+                         varying_vectors, ctx->Const.MaxVarying);
+            return false;
+         }
       }
    } else {
       const unsigned float_components = varying_vectors * 4;
       if (float_components > ctx->Const.MaxVarying * 4) {
-        linker_error(prog, "shader uses too many varying components "
-                     "(%u > %u)\n",
-                     float_components, ctx->Const.MaxVarying * 4);
-        return false;
+         if (ctx->Const.GLSLSkipStrictMaxVaryingLimitCheck) {
+            linker_warning(prog, "shader uses too many varying components "
+                           "(%u > %u), but the driver will try to optimize "
+                           "them out; this is non-portable out-of-spec "
+                           "behavior\n",
+                           float_components, ctx->Const.MaxVarying * 4);
+         } else {
+            linker_error(prog, "shader uses too many varying components "
+                         "(%u > %u)\n",
+                         float_components, ctx->Const.MaxVarying * 4);
+            return false;
+         }
       }
    }
 
@@ -1637,9 +1970,162 @@ assign_varying_locations(struct gl_context *ctx,
 }
 
 
+/**
+ * Store transform feedback location assignments into
+ * prog->LinkedTransformFeedback based on the data stored in tfeedback_decls.
+ *
+ * If an error occurs, the error is reported through linker_error() and false
+ * is returned.
+ */
+static bool
+store_tfeedback_info(struct gl_context *ctx, struct gl_shader_program *prog,
+                     unsigned num_tfeedback_decls,
+                     tfeedback_decl *tfeedback_decls)
+{
+   bool separate_attribs_mode =
+      prog->TransformFeedback.BufferMode == GL_SEPARATE_ATTRIBS;
+
+   ralloc_free(prog->LinkedTransformFeedback.Varyings);
+   ralloc_free(prog->LinkedTransformFeedback.Outputs);
+
+   memset(&prog->LinkedTransformFeedback, 0,
+          sizeof(prog->LinkedTransformFeedback));
+
+   prog->LinkedTransformFeedback.NumBuffers =
+      separate_attribs_mode ? num_tfeedback_decls : 1;
+
+   prog->LinkedTransformFeedback.Varyings =
+      rzalloc_array(prog,
+                   struct gl_transform_feedback_varying_info,
+                   num_tfeedback_decls);
+
+   unsigned num_outputs = 0;
+   for (unsigned i = 0; i < num_tfeedback_decls; ++i)
+      if (!tfeedback_decls[i].accumulate_num_outputs(prog, &num_outputs))
+         return false;
+
+   prog->LinkedTransformFeedback.Outputs =
+      rzalloc_array(prog,
+                    struct gl_transform_feedback_output,
+                    num_outputs);
+
+   for (unsigned i = 0; i < num_tfeedback_decls; ++i) {
+      unsigned buffer = separate_attribs_mode ? i : 0;
+      if (!tfeedback_decls[i].store(ctx, prog, &prog->LinkedTransformFeedback,
+                                    buffer, i, num_outputs))
+         return false;
+   }
+   assert(prog->LinkedTransformFeedback.NumOutputs == num_outputs);
+
+   return true;
+}
+
+/**
+ * Store the gl_FragDepth layout in the gl_shader_program struct.
+ */
+static void
+store_fragdepth_layout(struct gl_shader_program *prog)
+{
+   if (prog->_LinkedShaders[MESA_SHADER_FRAGMENT] == NULL) {
+      return;
+   }
+
+   struct exec_list *ir = prog->_LinkedShaders[MESA_SHADER_FRAGMENT]->ir;
+
+   /* We don't look up the gl_FragDepth symbol directly because if
+    * gl_FragDepth is not used in the shader, it's removed from the IR.
+    * However, the symbol won't be removed from the symbol table.
+    *
+    * We're only interested in the cases where the variable is NOT removed
+    * from the IR.
+    */
+   foreach_list(node, ir) {
+      ir_variable *const var = ((ir_instruction *) node)->as_variable();
+
+      if (var == NULL || var->mode != ir_var_out) {
+         continue;
+      }
+
+      if (strcmp(var->name, "gl_FragDepth") == 0) {
+         switch (var->depth_layout) {
+         case ir_depth_layout_none:
+            prog->FragDepthLayout = FRAG_DEPTH_LAYOUT_NONE;
+            return;
+         case ir_depth_layout_any:
+            prog->FragDepthLayout = FRAG_DEPTH_LAYOUT_ANY;
+            return;
+         case ir_depth_layout_greater:
+            prog->FragDepthLayout = FRAG_DEPTH_LAYOUT_GREATER;
+            return;
+         case ir_depth_layout_less:
+            prog->FragDepthLayout = FRAG_DEPTH_LAYOUT_LESS;
+            return;
+         case ir_depth_layout_unchanged:
+            prog->FragDepthLayout = FRAG_DEPTH_LAYOUT_UNCHANGED;
+            return;
+         default:
+            assert(0);
+            return;
+         }
+      }
+   }
+}
+
+/**
+ * Validate the resources used by a program versus the implementation limits
+ */
+static bool
+check_resources(struct gl_context *ctx, struct gl_shader_program *prog)
+{
+   static const char *const shader_names[MESA_SHADER_TYPES] = {
+      "vertex", "fragment", "geometry"
+   };
+
+   const unsigned max_samplers[MESA_SHADER_TYPES] = {
+      ctx->Const.MaxVertexTextureImageUnits,
+      ctx->Const.MaxTextureImageUnits,
+      ctx->Const.MaxGeometryTextureImageUnits
+   };
+
+   const unsigned max_uniform_components[MESA_SHADER_TYPES] = {
+      ctx->Const.VertexProgram.MaxUniformComponents,
+      ctx->Const.FragmentProgram.MaxUniformComponents,
+      0          /* FINISHME: Geometry shaders. */
+   };
+
+   for (unsigned i = 0; i < MESA_SHADER_TYPES; i++) {
+      struct gl_shader *sh = prog->_LinkedShaders[i];
+
+      if (sh == NULL)
+        continue;
+
+      if (sh->num_samplers > max_samplers[i]) {
+        linker_error(prog, "Too many %s shader texture samplers",
+                     shader_names[i]);
+      }
+
+      if (sh->num_uniform_components > max_uniform_components[i]) {
+         if (ctx->Const.GLSLSkipStrictMaxUniformLimitCheck) {
+            linker_warning(prog, "Too many %s shader uniform components, "
+                           "but the driver will try to optimize them out; "
+                           "this is non-portable out-of-spec behavior\n",
+                           shader_names[i]);
+         } else {
+            linker_error(prog, "Too many %s shader uniform components",
+                         shader_names[i]);
+         }
+      }
+   }
+
+   return prog->LinkStatus;
+}
+
 void
 link_shaders(struct gl_context *ctx, struct gl_shader_program *prog)
 {
+   tfeedback_decl *tfeedback_decls = NULL;
+   unsigned num_tfeedback_decls = prog->TransformFeedback.NumVarying;
+
    void *mem_ctx = ralloc_context(NULL); // temporary linker context
 
    prog->LinkStatus = false;
@@ -1806,19 +2292,54 @@ link_shaders(struct gl_context *ctx, struct gl_shader_program *prog)
         break;
    }
 
+   if (num_tfeedback_decls != 0) {
+      /* From GL_EXT_transform_feedback:
+       *   A program will fail to link if:
+       *
+       *   * the <count> specified by TransformFeedbackVaryingsEXT is
+       *     non-zero, but the program object has no vertex or geometry
+       *     shader;
+       */
+      if (prev >= MESA_SHADER_FRAGMENT) {
+         linker_error(prog, "Transform feedback varyings specified, but "
+                      "no vertex or geometry shader is present.");
+         goto done;
+      }
+
+      tfeedback_decls = ralloc_array(mem_ctx, tfeedback_decl,
+                                     prog->TransformFeedback.NumVarying);
+      if (!parse_tfeedback_decls(ctx, prog, mem_ctx, num_tfeedback_decls,
+                                 prog->TransformFeedback.VaryingNames,
+                                 tfeedback_decls))
+         goto done;
+   }
+
    for (unsigned i = prev + 1; i < MESA_SHADER_TYPES; i++) {
       if (prog->_LinkedShaders[i] == NULL)
         continue;
 
-      if (!assign_varying_locations(ctx, prog,
-                                   prog->_LinkedShaders[prev],
-                                   prog->_LinkedShaders[i])) {
+      if (!assign_varying_locations(
+             ctx, prog, prog->_LinkedShaders[prev], prog->_LinkedShaders[i],
+             i == MESA_SHADER_FRAGMENT ? num_tfeedback_decls : 0,
+             tfeedback_decls))
         goto done;
-      }
 
       prev = i;
    }
 
+   if (prev != MESA_SHADER_FRAGMENT && num_tfeedback_decls != 0) {
+      /* There was no fragment shader, but we still have to assign varying
+       * locations for use by transform feedback.
+       */
+      if (!assign_varying_locations(
+             ctx, prog, prog->_LinkedShaders[prev], NULL, num_tfeedback_decls,
+             tfeedback_decls))
+         goto done;
+   }
+
+   if (!store_tfeedback_info(ctx, prog, num_tfeedback_decls, tfeedback_decls))
+      goto done;
+
    if (prog->_LinkedShaders[MESA_SHADER_VERTEX] != NULL) {
       demote_shader_inputs_and_outputs(prog->_LinkedShaders[MESA_SHADER_VERTEX],
                                       ir_var_out);
@@ -1859,6 +2380,10 @@ link_shaders(struct gl_context *ctx, struct gl_shader_program *prog)
 
    update_array_sizes(prog);
    link_assign_uniform_locations(prog);
+   store_fragdepth_layout(prog);
+
+   if (!check_resources(ctx, prog))
+      goto done;
 
    /* OpenGL ES requires that a vertex shader and a fragment shader both be
     * present in a linked program.  By checking for use of shading language