X-Git-Url: https://git.libre-soc.org/?a=blobdiff_plain;f=src%2Fglsl%2Flink_varyings.cpp;h=51cbdaa0e6e2e78cb72951b89825845f10994260;hb=a760c73853e3d1bd4243f22fa968ce21a8eea0d6;hp=b9c3f5d434d71fd9bff44214b23e211b916296ff;hpb=4b11b57ab47b98b335a7212080dbccef04542805;p=mesa.git diff --git a/src/glsl/link_varyings.cpp b/src/glsl/link_varyings.cpp index b9c3f5d434d..51cbdaa0e6e 100644 --- a/src/glsl/link_varyings.cpp +++ b/src/glsl/link_varyings.cpp @@ -31,10 +31,13 @@ #include "main/mtypes.h" #include "glsl_symbol_table.h" +#include "glsl_parser_extras.h" #include "ir_optimization.h" #include "linker.h" #include "link_varyings.h" #include "main/macros.h" +#include "program/hash_table.h" +#include "program.h" /** @@ -45,19 +48,17 @@ cross_validate_outputs_to_inputs(struct gl_shader_program *prog, gl_shader *producer, gl_shader *consumer) { glsl_symbol_table parameters; - /* FINISHME: Figure these out dynamically. */ - const char *const producer_stage = "vertex"; - const char *const consumer_stage = "fragment"; + const char *const producer_stage = + _mesa_glsl_shader_target_name(producer->Type); + const char *const consumer_stage = + _mesa_glsl_shader_target_name(consumer->Type); /* Find all shader outputs in the "producer" stage. */ foreach_list(node, producer->ir) { ir_variable *const var = ((ir_instruction *) node)->as_variable(); - /* FINISHME: For geometry shaders, this should also look for inout - * FINISHME: variables. - */ - if ((var == NULL) || (var->mode != ir_var_out)) + if ((var == NULL) || (var->mode != ir_var_shader_out)) continue; parameters.add_variable(var); @@ -71,10 +72,7 @@ cross_validate_outputs_to_inputs(struct gl_shader_program *prog, foreach_list(node, consumer->ir) { ir_variable *const input = ((ir_instruction *) node)->as_variable(); - /* FINISHME: For geometry shaders, this should also look for inout - * FINISHME: variables. - */ - if ((input == NULL) || (input->mode != ir_var_in)) + if ((input == NULL) || (input->mode != ir_var_shader_in)) continue; ir_variable *const output = parameters.get_variable(input->name); @@ -160,10 +158,13 @@ cross_validate_outputs_to_inputs(struct gl_shader_program *prog, /** * 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. + * glTransformFeedbackVaryings. + * + * If the input is mal-formed, this call still succeeds, but it sets + * this->var_name to a mal-formed input, so tfeedback_decl::find_output_var() + * will fail to find any matching variable. */ -bool +void tfeedback_decl::init(struct gl_context *ctx, struct gl_shader_program *prog, const void *mem_ctx, const char *input) { @@ -176,12 +177,13 @@ tfeedback_decl::init(struct gl_context *ctx, struct gl_shader_program *prog, this->is_clip_distance_mesa = false; this->skip_components = 0; this->next_buffer_separator = false; + this->matched_candidate = NULL; if (ctx->Extensions.ARB_transform_feedback3) { /* Parse gl_NextBuffer. */ if (strcmp(input, "gl_NextBuffer") == 0) { this->next_buffer_separator = true; - return true; + return; } /* Parse gl_SkipComponents. */ @@ -195,21 +197,17 @@ tfeedback_decl::init(struct gl_context *ctx, struct gl_shader_program *prog, this->skip_components = 4; if (this->skip_components) - return true; + return; } /* 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; - } + const char *base_name_end; + long subscript = parse_program_resource_name(input, &base_name_end); + this->var_name = ralloc_strndup(mem_ctx, input, base_name_end - input); + if (subscript >= 0) { + this->array_subscript = subscript; this->is_subscripted = true; } else { - this->var_name = ralloc_strdup(mem_ctx, input); this->is_subscripted = false; } @@ -221,8 +219,6 @@ tfeedback_decl::init(struct gl_context *ctx, struct gl_shader_program *prog, strcmp(this->var_name, "gl_ClipDistance") == 0) { this->is_clip_distance_mesa = true; } - - return true; } @@ -246,27 +242,32 @@ tfeedback_decl::is_same(const tfeedback_decl &x, const tfeedback_decl &y) /** - * Assign a location for this tfeedback_decl object based on the location - * assignment in output_var. + * Assign a location for this tfeedback_decl object based on the transform + * feedback candidate found by find_candidate. * * 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) + struct gl_shader_program *prog) { assert(this->is_varying()); - if (output_var->type->is_array()) { + unsigned fine_location + = this->matched_candidate->toplevel_var->location * 4 + + this->matched_candidate->toplevel_var->location_frac + + this->matched_candidate->offset; + + if (this->matched_candidate->type->is_array()) { /* Array variable */ const unsigned matrix_cols = - output_var->type->fields.array->matrix_columns; + this->matched_candidate->type->fields.array->matrix_columns; const unsigned vector_elements = - output_var->type->fields.array->vector_elements; + this->matched_candidate->type->fields.array->vector_elements; unsigned actual_array_size = this->is_clip_distance_mesa ? - prog->Vert.ClipDistanceArraySize : output_var->type->array_size(); + prog->Vert.ClipDistanceArraySize : + this->matched_candidate->type->array_size(); if (this->is_subscripted) { /* Check array bounds. */ @@ -277,22 +278,11 @@ tfeedback_decl::assign_location(struct gl_context *ctx, actual_array_size); return false; } - if (this->is_clip_distance_mesa) { - this->location = - output_var->location + this->array_subscript / 4; - this->location_frac = this->array_subscript % 4; - } else { - unsigned fine_location - = output_var->location * 4 + output_var->location_frac; - unsigned array_elem_size = vector_elements * matrix_cols; - fine_location += array_elem_size * this->array_subscript; - this->location = fine_location / 4; - this->location_frac = fine_location % 4; - } + unsigned array_elem_size = this->is_clip_distance_mesa ? + 1 : vector_elements * matrix_cols; + fine_location += array_elem_size * this->array_subscript; this->size = 1; } else { - this->location = output_var->location; - this->location_frac = output_var->location_frac; this->size = actual_array_size; } this->vector_elements = vector_elements; @@ -300,7 +290,7 @@ tfeedback_decl::assign_location(struct gl_context *ctx, if (this->is_clip_distance_mesa) this->type = GL_FLOAT; else - this->type = output_var->type->fields.array->gl_type; + this->type = this->matched_candidate->type->fields.array->gl_type; } else { /* Regular variable (scalar, vector, or matrix) */ if (this->is_subscripted) { @@ -309,13 +299,13 @@ tfeedback_decl::assign_location(struct gl_context *ctx, this->orig_name, this->var_name); return false; } - this->location = output_var->location; - this->location_frac = output_var->location_frac; 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; + this->vector_elements = this->matched_candidate->type->vector_elements; + this->matrix_columns = this->matched_candidate->type->matrix_columns; + this->type = this->matched_candidate->type->gl_type; } + this->location = fine_location / 4; + this->location_frac = fine_location % 4; /* From GL_EXT_transform_feedback: * A program will fail to link if: @@ -410,26 +400,26 @@ tfeedback_decl::store(struct gl_context *ctx, struct gl_shader_program *prog, } -ir_variable * -tfeedback_decl::find_output_var(gl_shader_program *prog, - gl_shader *producer) const +const tfeedback_candidate * +tfeedback_decl::find_candidate(gl_shader_program *prog, + hash_table *tfeedback_candidates) { const char *name = this->is_clip_distance_mesa ? "gl_ClipDistanceMESA" : this->var_name; - ir_variable *var = producer->symbols->get_variable(name); - if (var && var->mode == ir_var_out) - return var; - - /* From GL_EXT_transform_feedback: - * A program will fail to link if: - * - * * any variable name specified in the 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 NULL; + this->matched_candidate = (const tfeedback_candidate *) + hash_table_find(tfeedback_candidates, name); + if (!this->matched_candidate) { + /* From GL_EXT_transform_feedback: + * A program will fail to link if: + * + * * any variable name specified in the 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 this->matched_candidate; } @@ -446,8 +436,7 @@ parse_tfeedback_decls(struct gl_context *ctx, struct gl_shader_program *prog, 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; + decls[i].init(ctx, prog, mem_ctx, varying_names[i]); if (!decls[i].is_varying()) continue; @@ -554,7 +543,7 @@ store_tfeedback_info(struct gl_context *ctx, struct gl_shader_program *prog, class varying_matches { public: - varying_matches(bool disable_varying_packing); + varying_matches(bool disable_varying_packing, bool consumer_is_fs); ~varying_matches(); void record(ir_variable *producer_var, ir_variable *consumer_var); unsigned assign_locations(); @@ -617,8 +606,8 @@ private: /** * The location which has been assigned for this varying. This is * expressed in multiples of a float, with the first generic varying - * (i.e. the one referred to by VERT_RESULT_VAR0 or FRAG_ATTRIB_VAR0) - * represented by the value 0. + * (i.e. the one referred to by VARYING_SLOT_VAR0) represented by the + * value 0. */ unsigned generic_location; } *matches; @@ -634,11 +623,15 @@ private: * it was allocated. */ unsigned matches_capacity; + + const bool consumer_is_fs; }; -varying_matches::varying_matches(bool disable_varying_packing) - : disable_varying_packing(disable_varying_packing) +varying_matches::varying_matches(bool disable_varying_packing, + bool consumer_is_fs) + : disable_varying_packing(disable_varying_packing), + consumer_is_fs(consumer_is_fs) { /* Note: this initial capacity is rather arbitrarily chosen to be large * enough for many cases without wasting an unreasonable amount of space. @@ -669,6 +662,10 @@ varying_matches::~varying_matches() * If \c producer_var has already been paired up with a consumer_var, or * producer_var is part of fixed pipeline functionality (and hence already has * a location assigned), this function has no effect. + * + * Note: as a side effect this function may change the interpolation type of + * \c producer_var, but only when the change couldn't possibly affect + * rendering. */ void varying_matches::record(ir_variable *producer_var, ir_variable *consumer_var) @@ -681,6 +678,25 @@ varying_matches::record(ir_variable *producer_var, ir_variable *consumer_var) return; } + if ((consumer_var == NULL && producer_var->type->contains_integer()) || + !consumer_is_fs) { + /* Since this varying is not being consumed by the fragment shader, its + * interpolation type varying cannot possibly affect rendering. Also, + * this variable is non-flat and is (or contains) an integer. + * + * lower_packed_varyings requires all integer varyings to flat, + * regardless of where they appear. We can trivially satisfy that + * requirement by changing the interpolation type to flat here. + */ + producer_var->centroid = false; + producer_var->interpolation = INTERP_QUALIFIER_FLAT; + + if (consumer_var) { + consumer_var->centroid = false; + consumer_var->interpolation = INTERP_QUALIFIER_FLAT; + } + } + if (this->num_matches == this->matches_capacity) { this->matches_capacity *= 2; this->matches = (match *) @@ -777,17 +793,25 @@ varying_matches::store_locations(unsigned producer_base, unsigned varying_matches::compute_packing_class(ir_variable *var) { - /* In this initial implementation we conservatively assume that variables - * can only be packed if their base type (float/int/uint/bool) matches and - * their interpolation and centroid qualifiers match. + /* Without help from the back-end, there is no way to pack together + * variables with different interpolation types, because + * lower_packed_varyings must choose exactly one interpolation type for + * each packed varying it creates. + * + * However, we can safely pack together floats, ints, and uints, because: * - * TODO: relax these restrictions when the driver back-end permits. + * - varyings of base type "int" and "uint" must use the "flat" + * interpolation type, which can only occur in GLSL 1.30 and above. + * + * - On platforms that support GLSL 1.30 and above, lower_packed_varyings + * can store flat floats as ints without losing any information (using + * the ir_unop_bitcast_* opcodes). + * + * Therefore, the packing class depends only on the interpolation type. */ unsigned packing_class = var->centroid ? 1 : 0; packing_class *= 4; packing_class += var->interpolation; - packing_class *= GLSL_TYPE_ERROR; - packing_class += var->type->get_scalar_type()->base_type; return packing_class; } @@ -802,16 +826,15 @@ varying_matches::compute_packing_order(ir_variable *var) { const glsl_type *element_type = var->type; - /* FINISHME: Support for "varying" records in GLSL 1.50. */ while (element_type->base_type == GLSL_TYPE_ARRAY) { element_type = element_type->fields.array; } - switch (element_type->vector_elements) { + switch (element_type->component_slots() % 4) { case 1: return PACKING_ORDER_SCALAR; case 2: return PACKING_ORDER_VEC2; case 3: return PACKING_ORDER_VEC3; - case 4: return PACKING_ORDER_VEC4; + case 0: return PACKING_ORDER_VEC4; default: assert(!"Unexpected value of vector_elements"); return PACKING_ORDER_VEC4; @@ -846,11 +869,11 @@ 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->mode == ir_var_shader_in) { switch (var->location) { - case FRAG_ATTRIB_WPOS: - case FRAG_ATTRIB_FACE: - case FRAG_ATTRIB_PNTC: + case VARYING_SLOT_POS: + case VARYING_SLOT_FACE: + case VARYING_SLOT_PNTC: return false; default: return true; @@ -860,6 +883,82 @@ is_varying_var(GLenum shaderType, const ir_variable *var) } +/** + * Visitor class that generates tfeedback_candidate structs describing all + * possible targets of transform feedback. + * + * tfeedback_candidate structs are stored in the hash table + * tfeedback_candidates, which is passed to the constructor. This hash table + * maps varying names to instances of the tfeedback_candidate struct. + */ +class tfeedback_candidate_generator : public program_resource_visitor +{ +public: + tfeedback_candidate_generator(void *mem_ctx, + hash_table *tfeedback_candidates) + : mem_ctx(mem_ctx), + tfeedback_candidates(tfeedback_candidates), + toplevel_var(NULL), + varying_floats(0) + { + } + + void process(ir_variable *var) + { + this->toplevel_var = var; + this->varying_floats = 0; + if (var->is_interface_instance()) + program_resource_visitor::process(var->interface_type, + var->interface_type->name); + else + program_resource_visitor::process(var); + } + +private: + virtual void visit_field(const glsl_type *type, const char *name, + bool row_major) + { + assert(!type->is_record()); + assert(!(type->is_array() && type->fields.array->is_record())); + assert(!type->is_interface()); + assert(!(type->is_array() && type->fields.array->is_interface())); + + (void) row_major; + + tfeedback_candidate *candidate + = rzalloc(this->mem_ctx, tfeedback_candidate); + candidate->toplevel_var = this->toplevel_var; + candidate->type = type; + candidate->offset = this->varying_floats; + hash_table_insert(this->tfeedback_candidates, candidate, + ralloc_strdup(this->mem_ctx, name)); + this->varying_floats += type->component_slots(); + } + + /** + * Memory context used to allocate hash table keys and values. + */ + void * const mem_ctx; + + /** + * Hash table in which tfeedback_candidate objects should be stored. + */ + hash_table * const tfeedback_candidates; + + /** + * Pointer to the toplevel variable that is being traversed. + */ + ir_variable *toplevel_var; + + /** + * Total number of varying floats that have been visited so far. This is + * used to determine the offset to each varying within the toplevel + * variable. + */ + unsigned varying_floats; +}; + + /** * Assign locations for all variables that are produced in one pipeline stage * (the "producer") and consumed in the next stage (the "consumer"). @@ -888,10 +987,16 @@ assign_varying_locations(struct gl_context *ctx, unsigned num_tfeedback_decls, tfeedback_decl *tfeedback_decls) { - /* FINISHME: Set dynamically when geometry shader support is added. */ - const unsigned producer_base = VERT_RESULT_VAR0; - const unsigned consumer_base = FRAG_ATTRIB_VAR0; - varying_matches matches(ctx->Const.DisableVaryingPacking); + const unsigned producer_base = VARYING_SLOT_VAR0; + const unsigned consumer_base = VARYING_SLOT_VAR0; + varying_matches matches(ctx->Const.DisableVaryingPacking, + consumer && consumer->Type == GL_FRAGMENT_SHADER); + hash_table *tfeedback_candidates + = hash_table_ctor(0, hash_table_string_hash, hash_table_string_compare); + hash_table *consumer_inputs + = hash_table_ctor(0, hash_table_string_hash, hash_table_string_compare); + hash_table *consumer_interface_inputs + = hash_table_ctor(0, hash_table_string_hash, hash_table_string_compare); /* Operate in a total of three passes. * @@ -904,16 +1009,51 @@ assign_varying_locations(struct gl_context *ctx, * not being inputs. This lets the optimizer eliminate them. */ + if (consumer) { + foreach_list(node, consumer->ir) { + ir_variable *const input_var = + ((ir_instruction *) node)->as_variable(); + + if ((input_var != NULL) && (input_var->mode == ir_var_shader_in)) { + if (input_var->interface_type != NULL) { + char *const iface_field_name = + ralloc_asprintf(mem_ctx, "%s.%s", + input_var->interface_type->name, + input_var->name); + hash_table_insert(consumer_interface_inputs, input_var, + iface_field_name); + } else { + hash_table_insert(consumer_inputs, input_var, + ralloc_strdup(mem_ctx, input_var->name)); + } + } + } + } + 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)) + if ((output_var == NULL) || (output_var->mode != ir_var_shader_out)) continue; - ir_variable *input_var = - consumer ? consumer->symbols->get_variable(output_var->name) : NULL; + tfeedback_candidate_generator g(mem_ctx, tfeedback_candidates); + g.process(output_var); + + ir_variable *input_var; + if (output_var->interface_type != NULL) { + char *const iface_field_name = + ralloc_asprintf(mem_ctx, "%s.%s", + output_var->interface_type->name, + output_var->name); + input_var = + (ir_variable *) hash_table_find(consumer_interface_inputs, + iface_field_name); + } else { + input_var = + (ir_variable *) hash_table_find(consumer_inputs, output_var->name); + } - if (input_var && input_var->mode != ir_var_in) + if (input_var && input_var->mode != ir_var_shader_in) input_var = NULL; if (input_var) { @@ -925,15 +1065,18 @@ assign_varying_locations(struct gl_context *ctx, if (!tfeedback_decls[i].is_varying()) continue; - ir_variable *output_var - = tfeedback_decls[i].find_output_var(prog, producer); + const tfeedback_candidate *matched_candidate + = tfeedback_decls[i].find_candidate(prog, tfeedback_candidates); - if (output_var == NULL) + if (matched_candidate == NULL) { + hash_table_dtor(tfeedback_candidates); + hash_table_dtor(consumer_inputs); + hash_table_dtor(consumer_interface_inputs); return false; - - if (output_var->is_unmatched_generic_inout) { - matches.record(output_var, NULL); } + + if (matched_candidate->toplevel_var->is_unmatched_generic_inout) + matches.record(matched_candidate->toplevel_var, NULL); } const unsigned slots_used = matches.assign_locations(); @@ -943,13 +1086,18 @@ assign_varying_locations(struct gl_context *ctx, if (!tfeedback_decls[i].is_varying()) continue; - ir_variable *output_var - = tfeedback_decls[i].find_output_var(prog, producer); - - if (!tfeedback_decls[i].assign_location(ctx, prog, output_var)) + if (!tfeedback_decls[i].assign_location(ctx, prog)) { + hash_table_dtor(tfeedback_candidates); + hash_table_dtor(consumer_inputs); + hash_table_dtor(consumer_interface_inputs); return false; + } } + hash_table_dtor(tfeedback_candidates); + hash_table_dtor(consumer_inputs); + hash_table_dtor(consumer_interface_inputs); + if (ctx->Const.DisableVaryingPacking) { /* Transform feedback code assumes varyings are packed, so if the driver * has disabled varying packing, make sure it does not support transform @@ -957,24 +1105,20 @@ assign_varying_locations(struct gl_context *ctx, */ assert(!ctx->Extensions.EXT_transform_feedback); } else { - lower_packed_varyings(mem_ctx, producer_base, slots_used, ir_var_out, - producer); + lower_packed_varyings(mem_ctx, producer_base, slots_used, + ir_var_shader_out, producer); if (consumer) { - lower_packed_varyings(mem_ctx, consumer_base, slots_used, ir_var_in, - consumer); + lower_packed_varyings(mem_ctx, consumer_base, slots_used, + ir_var_shader_in, consumer); } } - unsigned varying_vectors = 0; - 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->is_unmatched_generic_inout) { + if (var && var->mode == ir_var_shader_in && + var->is_unmatched_generic_inout) { if (prog->Version <= 120) { /* On page 25 (page 31 of the PDF) of the GLSL 1.20 spec: * @@ -989,53 +1133,57 @@ assign_varying_locations(struct gl_context *ctx, * "glsl1-varying read but not written" in piglit. */ - linker_error(prog, "fragment shader varying %s not written " - "by vertex shader\n.", var->name); + linker_error(prog, "%s shader varying %s not written " + "by %s shader\n.", + _mesa_glsl_shader_target_name(consumer->Type), + var->name, + _mesa_glsl_shader_target_name(producer->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 if (is_varying_var(consumer->Type, var)) { - /* The packing rules are used for vertex shader inputs are also - * used for fragment shader inputs. - */ - varying_vectors += count_attribute_slots(var->type); } } } + return true; +} + +bool +check_against_varying_limit(struct gl_context *ctx, + struct gl_shader_program *prog, + gl_shader *consumer) +{ + unsigned varying_vectors = 0; + + foreach_list(node, consumer->ir) { + ir_variable *const var = ((ir_instruction *) node)->as_variable(); + + if (var && var->mode == ir_var_shader_in && + is_varying_var(consumer->Type, var)) { + /* The packing rules 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->IsES) { if (varying_vectors > ctx->Const.MaxVarying) { - 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; - } + 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) { - 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; - } + linker_error(prog, "shader uses too many varying components " + "(%u > %u)\n", + float_components, ctx->Const.MaxVarying * 4); + return false; } }