[Mesa-dev] [PATCH v2] glsl: Assign transform feedback varying slots in linker.
Marek Olšák
maraeo at gmail.com
Tue Nov 8 05:15:47 PST 2011
On Tue, Nov 8, 2011 at 7:28 AM, Paul Berry <stereotype441 at gmail.com> wrote:
> This patch modifies the GLSL linker to assign additional slots for
> varying variables used by transform feedback, and record the varying
> slots used by transform feedback for use by the driver back-end.
>
> This required modifying assign_varying_locations() so that it assigns
> a varying location if either (a) the varying is used by the next stage
> of the GL pipeline, or (b) the varying is required by transform
> feedback. In order to avoid duplicating the code to assign a single
> varying location, I moved it into its own function,
> assign_varying_location().
>
> In addition, to support transform feedback in the case where there is
> no fragment shader, it is now possible to call
> assign_varying_locations() with a consumer of NULL.
> ---
> Changes from v1:
>
> - Fixed loop bound in tfeedback_decl::store() (was this->vector_elements,
> should have been this->matrix_columns).
>
> - Fixed the case where transform feedback is in use but there is no fragment
> shader.
>
> src/glsl/linker.cpp | 552 ++++++++++++++++++++++++++++++++++++++++++------
> src/mesa/main/mtypes.h | 13 ++
> 2 files changed, 502 insertions(+), 63 deletions(-)
>
> diff --git a/src/glsl/linker.cpp b/src/glsl/linker.cpp
> index 915d5bb..5cccd7f 100644
> --- a/src/glsl/linker.cpp
> +++ b/src/glsl/linker.cpp
> @@ -1519,10 +1519,358 @@ 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_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 store(struct gl_shader_program *prog,
> + struct gl_transform_feedback_info *info, unsigned buffer) 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
> + {
> + 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
> + {
> + return this->vector_elements * this->matrix_columns;
> + }
> +
> +private:
> + /**
> + * The name that was supplied to glTransformFeedbackVaryings. Used for
> + * error reporting.
> + */
> + const char *orig_name;
> +
> + /**
> + * The name of the variable, parsed from orig_name.
> + */
> + char *var_name;
> +
> + /**
> + * True if the declaration in orig_name represents an array.
> + */
> + bool is_array;
> +
> + /**
> + * If is_array is true, the array index that was specified in orig_name.
> + */
> + unsigned array_index;
> +
> + /**
> + * 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;
> +};
> +
> +
> +/**
> + * 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_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;
> +
> + const char *bracket = strrchr(input, '[');
> +
> + if (bracket) {
> + this->var_name = ralloc_strndup(mem_ctx, input, bracket - input);
> + if (sscanf(bracket, "[%u]", &this->array_index) == 1) {
> + this->is_array = true;
> + return true;
> + }
> + } else {
> + this->var_name = ralloc_strdup(mem_ctx, input);
> + this->is_array = false;
> + return true;
> + }
> +
> + linker_error(prog, "Cannot parse transform feedback varying %s", input);
> + return false;
> +}
> +
> +
> +/**
> + * 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_array != y.is_array)
> + return false;
> + if (x.is_array && x.array_index != y.array_index)
> + 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 */
> + if (!this->is_array) {
> + linker_error(prog, "Transform feedback varying %s found, "
> + "but it's not an array ([] not expected).",
> + this->orig_name);
> + return false;
> + }
> + /* Check array bounds. */
> + if (this->array_index >=
> + (unsigned) output_var->type->array_size()) {
> + linker_error(prog, "Transform feedback varying %s has index "
> + "%i, but the array size is %i.",
> + this->orig_name, this->array_index,
> + output_var->type->array_size());
> + }
> + const unsigned matrix_cols =
> + output_var->type->fields.array->matrix_columns;
> + this->location = output_var->location + this->array_index * matrix_cols;
> + this->vector_elements = output_var->type->fields.array->vector_elements;
> + this->matrix_columns = matrix_cols;
> + } else {
> + /* Regular variable (scalar, vector, or matrix) */
> + if (this->is_array) {
> + linker_error(prog, "Transform feedback varying %s found, "
> + "but it's an array ([] expected).",
> + this->orig_name);
> + return false;
> + }
> + this->location = output_var->location;
> + this->vector_elements = output_var->type->vector_elements;
> + this->matrix_columns = output_var->type->matrix_columns;
> + }
> + /* 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;
> +}
> +
> +
> +/**
> + * 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_shader_program *prog,
> + struct gl_transform_feedback_info *info,
> + unsigned buffer) const
> +{
> + 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;
> + }
> + for (unsigned v = 0; v < this->matrix_columns; ++v) {
> + info->Outputs[info->NumOutputs].OutputRegister = this->location + v;
> + info->Outputs[info->NumOutputs].NumComponents = this->vector_elements;
> + info->Outputs[info->NumOutputs].OutputBuffer = buffer;
> + ++info->NumOutputs;
> + }
> + 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_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(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;
The "return false" statement should be put next to linker_error.
Otherwise it always fails when the number of TFB varyings is at least
2.
With that fixed, this patch is:
Reviewed-by: Marek Olšák <maraeo at gmail.com>
Tested-by: Marek Olšák <maraeo at gmail.com>
Marek
> + }
> + }
> + 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,79 +1888,77 @@ assign_varying_locations(struct gl_context *ctx,
> */
>
> invalidate_variable_locations(producer, ir_var_out, VERT_RESULT_VAR0);
> - invalidate_variable_locations(consumer, ir_var_in, FRAG_ATTRIB_VAR0);
> + if (consumer)
> + 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);
> -
> - if ((input_var == NULL) || (input_var->mode != ir_var_in))
> - continue;
> -
> - 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());
> + ir_variable *input_var =
> + consumer ? consumer->symbols->get_variable(output_var->name) : NULL;
>
> - if (output_var->type->is_array()) {
> - const unsigned slots = output_var->type->length
> - * output_var->type->fields.array->matrix_columns;
> + if (input_var && input_var->mode != ir_var_in)
> + input_var = NULL;
>
> - 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);
> + }
> }
> }
>
> @@ -1637,9 +1983,54 @@ 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)
> +{
> + unsigned total_tfeedback_components = 0;
> + prog->LinkedTransformFeedback.NumOutputs = 0;
> + for (unsigned i = 0; i < num_tfeedback_decls; ++i) {
> + unsigned buffer =
> + prog->TransformFeedback.BufferMode == GL_SEPARATE_ATTRIBS ? i : 0;
> + if (!tfeedback_decls[i].store(prog, &prog->LinkedTransformFeedback,
> + buffer))
> + return false;
> + total_tfeedback_components += tfeedback_decls[i].num_components();
> + }
> +
> + /* 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 &&
> + total_tfeedback_components >
> + ctx->Const.MaxTransformFeedbackInterleavedComponents) {
> + linker_error(prog, "The MAX_TRANSFORM_FEEDBACK_INTERLEAVED_COMPONENTS "
> + "limit has been exceeded.");
> + return false;
> + }
> +
> + return true;
> +}
> +
> +
> 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 +2197,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(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);
> diff --git a/src/mesa/main/mtypes.h b/src/mesa/main/mtypes.h
> index 6190972..5df898c 100644
> --- a/src/mesa/main/mtypes.h
> +++ b/src/mesa/main/mtypes.h
> @@ -1818,6 +1818,16 @@ struct prog_instruction;
> struct gl_program_parameter_list;
> struct gl_uniform_list;
>
> +/** Post-link transform feedback info. */
> +struct gl_transform_feedback_info {
> + unsigned NumOutputs;
> +
> + struct {
> + unsigned OutputRegister;
> + unsigned OutputBuffer;
> + unsigned NumComponents;
> + } Outputs[MAX_PROGRAM_OUTPUTS];
> +};
>
> /**
> * Base class for any kind of program object
> @@ -2191,6 +2201,9 @@ struct gl_shader_program
> GLchar **VaryingNames; /**< Array [NumVarying] of char * */
> } TransformFeedback;
>
> + /** Post-link transform feedback info. */
> + struct gl_transform_feedback_info LinkedTransformFeedback;
> +
> /** Geometry shader state - copied into gl_geometry_program at link time */
> struct {
> GLint VerticesOut;
> --
> 1.7.6.4
>
>
More information about the mesa-dev
mailing list