+ /**
+ * 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;
+
+ /**
+ * How many components to skip. If non-zero, this is
+ * gl_SkipComponents{1,2,3,4} from ARB_transform_feedback3.
+ */
+ unsigned skip_components;
+
+ /**
+ * Whether this is gl_NextBuffer from ARB_transform_feedback3.
+ */
+ bool next_buffer_separator;
+};
+
+
+/**
+ * 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;
+ this->skip_components = 0;
+ this->next_buffer_separator = false;
+
+ if (ctx->Extensions.ARB_transform_feedback3) {
+ /* Parse gl_NextBuffer. */
+ if (strcmp(input, "gl_NextBuffer") == 0) {
+ this->next_buffer_separator = true;
+ return true;
+ }
+
+ /* Parse gl_SkipComponents. */
+ if (strcmp(input, "gl_SkipComponents1") == 0)
+ this->skip_components = 1;
+ else if (strcmp(input, "gl_SkipComponents2") == 0)
+ this->skip_components = 2;
+ else if (strcmp(input, "gl_SkipComponents3") == 0)
+ this->skip_components = 3;
+ else if (strcmp(input, "gl_SkipComponents4") == 0)
+ this->skip_components = 4;
+
+ if (this->skip_components)
+ return true;
+ }
+
+ /* Parse a declaration. */
+ 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)
+{
+ assert(x.is_varying() && y.is_varying());
+
+ 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)
+{
+ assert(this->is_varying());
+
+ 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_varying()) {
+ return true;
+ }
+
+ 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, const unsigned max_outputs) const
+{
+ assert(!this->next_buffer_separator);
+
+ /* Handle gl_SkipComponents. */
+ if (this->skip_components) {
+ info->BufferStride[buffer] += this->skip_components;
+ return true;
+ }
+
+ /* 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[info->NumVarying].Name = ralloc_strdup(prog, this->orig_name);
+ info->Varyings[info->NumVarying].Type = this->type;
+ info->Varyings[info->NumVarying].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;
+
+ if (!decls[i].is_varying())
+ continue;
+
+ /* 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 (!decls[j].is_varying())
+ continue;
+
+ 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;
+ }
+}
+
+
+/**
+ * Is the given variable a varying variable to be counted against the
+ * limit in ctx->Const.MaxVarying?
+ * This includes variables such as texcoords, colors and generic
+ * varyings, but excludes variables such as gl_FrontFacing and gl_FragCoord.
+ */
+static bool
+is_varying_var(GLenum shaderType, const ir_variable *var)
+{
+ /* Only fragment shaders will take a varying variable as an input */
+ if (shaderType == GL_FRAGMENT_SHADER &&
+ var->mode == ir_var_in &&
+ var->explicit_location) {
+ switch (var->location) {
+ case FRAG_ATTRIB_WPOS:
+ case FRAG_ATTRIB_FACE:
+ case FRAG_ATTRIB_PNTC:
+ return false;
+ default:
+ return true;
+ }
+ }
+ return false;
+}
+
+
+/**
+ * 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,
+ unsigned num_tfeedback_decls,
+ tfeedback_decl *tfeedback_decls)
+{
+ /* FINISHME: Set dynamically when geometry shader support is added. */
+ unsigned output_index = VERT_RESULT_VAR0;
+ unsigned input_index = FRAG_ATTRIB_VAR0;
+
+ /* Operate in a total of three passes.
+ *
+ * 1. Assign locations for any matching inputs and outputs.
+ *
+ * 2. Mark output variables in the producer that do not have locations as
+ * not being outputs. This lets the optimizer eliminate them.
+ *
+ * 3. Mark input variables in the consumer that do not have locations as
+ * not being inputs. This lets the optimizer eliminate them.
+ */
+
+ link_invalidate_variable_locations(producer, ir_var_out, VERT_RESULT_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();