Home | History | Annotate | Download | only in glsl
      1 /*
      2  * Copyright  2013 Marek Olk <maraeo (at) gmail.com>
      3  *
      4  * Permission is hereby granted, free of charge, to any person obtaining a
      5  * copy of this software and associated documentation files (the "Software"),
      6  * to deal in the Software without restriction, including without limitation
      7  * the rights to use, copy, modify, merge, publish, distribute, sublicense,
      8  * and/or sell copies of the Software, and to permit persons to whom the
      9  * Software is furnished to do so, subject to the following conditions:
     10  *
     11  * The above copyright notice and this permission notice (including the next
     12  * paragraph) shall be included in all copies or substantial portions of the
     13  * Software.
     14  *
     15  * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
     16  * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
     17  * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.  IN NO EVENT SHALL
     18  * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
     19  * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
     20  * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
     21  * DEALINGS IN THE SOFTWARE.
     22  */
     23 
     24 /**
     25  * \file opt_dead_builtin_varyings.cpp
     26  *
     27  * This eliminates the built-in shader outputs which are either not written
     28  * at all or not used by the next stage. It also eliminates unused elements
     29  * of gl_TexCoord inputs, which reduces the overall varying usage.
     30  * The varyings handled here are the primary and secondary color, the fog,
     31  * and the texture coordinates (gl_TexCoord).
     32  *
     33  * This pass is necessary, because the Mesa GLSL linker cannot eliminate
     34  * built-in varyings like it eliminates user-defined varyings, because
     35  * the built-in varyings have pre-assigned locations. Also, the elimination
     36  * of unused gl_TexCoord elements requires its own lowering pass anyway.
     37  *
     38  * It's implemented by replacing all occurrences of dead varyings with
     39  * temporary variables, which creates dead code. It is recommended to run
     40  * a dead-code elimination pass after this.
     41  *
     42  * If any texture coordinate slots can be eliminated, the gl_TexCoord array is
     43  * broken down into separate vec4 variables with locations equal to
     44  * VARYING_SLOT_TEX0 + i.
     45  *
     46  * The same is done for the gl_FragData fragment shader output.
     47  */
     48 
     49 #include "main/core.h" /* for snprintf and ARRAY_SIZE */
     50 #include "ir.h"
     51 #include "ir_rvalue_visitor.h"
     52 #include "ir_optimization.h"
     53 #include "ir_print_visitor.h"
     54 #include "compiler/glsl_types.h"
     55 #include "link_varyings.h"
     56 
     57 namespace {
     58 
     59 /**
     60  * This obtains detailed information about built-in varyings from shader code.
     61  */
     62 class varying_info_visitor : public ir_hierarchical_visitor {
     63 public:
     64    /* "mode" can be either ir_var_shader_in or ir_var_shader_out */
     65    varying_info_visitor(ir_variable_mode mode, bool find_frag_outputs = false)
     66       : lower_texcoord_array(true),
     67         texcoord_array(NULL),
     68         texcoord_usage(0),
     69         find_frag_outputs(find_frag_outputs),
     70         lower_fragdata_array(true),
     71         fragdata_array(NULL),
     72         fragdata_usage(0),
     73         color_usage(0),
     74         tfeedback_color_usage(0),
     75         fog(NULL),
     76         has_fog(false),
     77         tfeedback_has_fog(false),
     78         mode(mode)
     79    {
     80       memset(color, 0, sizeof(color));
     81       memset(backcolor, 0, sizeof(backcolor));
     82    }
     83 
     84    virtual ir_visitor_status visit_enter(ir_dereference_array *ir)
     85    {
     86       ir_variable *var = ir->variable_referenced();
     87 
     88       if (!var || var->data.mode != this->mode || !var->type->is_array() ||
     89           !is_gl_identifier(var->name))
     90          return visit_continue;
     91 
     92       /* Only match gl_FragData[], not gl_SecondaryFragDataEXT[] or
     93        * gl_LastFragData[].
     94        */
     95       if (this->find_frag_outputs && strcmp(var->name, "gl_FragData") == 0) {
     96          this->fragdata_array = var;
     97 
     98          ir_constant *index = ir->array_index->as_constant();
     99          if (index == NULL) {
    100             /* This is variable indexing. */
    101             this->fragdata_usage |= (1 << var->type->array_size()) - 1;
    102             this->lower_fragdata_array = false;
    103          }
    104          else {
    105             this->fragdata_usage |= 1 << index->get_uint_component(0);
    106             /* Don't lower fragdata array if the output variable
    107              * is not a float variable (or float vector) because it will
    108              * generate wrong register assignments because of different
    109              * data types.
    110              */
    111             if (var->type->gl_type != GL_FLOAT &&
    112                 var->type->gl_type != GL_FLOAT_VEC2 &&
    113                 var->type->gl_type != GL_FLOAT_VEC3 &&
    114                 var->type->gl_type != GL_FLOAT_VEC4)
    115                this->lower_fragdata_array = false;
    116          }
    117 
    118          /* Don't visit the leaves of ir_dereference_array. */
    119          return visit_continue_with_parent;
    120       }
    121 
    122       if (!this->find_frag_outputs && var->data.location == VARYING_SLOT_TEX0) {
    123          this->texcoord_array = var;
    124 
    125          ir_constant *index = ir->array_index->as_constant();
    126          if (index == NULL) {
    127             /* There is variable indexing, we can't lower the texcoord array.
    128              */
    129             this->texcoord_usage |= (1 << var->type->array_size()) - 1;
    130             this->lower_texcoord_array = false;
    131          }
    132          else {
    133             this->texcoord_usage |= 1 << index->get_uint_component(0);
    134          }
    135 
    136          /* Don't visit the leaves of ir_dereference_array. */
    137          return visit_continue_with_parent;
    138       }
    139 
    140       return visit_continue;
    141    }
    142 
    143    virtual ir_visitor_status visit(ir_dereference_variable *ir)
    144    {
    145       ir_variable *var = ir->variable_referenced();
    146 
    147       if (var->data.mode != this->mode || !var->type->is_array())
    148          return visit_continue;
    149 
    150       if (this->find_frag_outputs && var->data.location == FRAG_RESULT_DATA0 &&
    151           var->data.index == 0) {
    152          /* This is a whole array dereference. */
    153          this->fragdata_usage |= (1 << var->type->array_size()) - 1;
    154          this->lower_fragdata_array = false;
    155          return visit_continue;
    156       }
    157 
    158       if (!this->find_frag_outputs && var->data.location == VARYING_SLOT_TEX0) {
    159          /* This is a whole array dereference like "gl_TexCoord = x;",
    160           * there's probably no point in lowering that.
    161           */
    162          this->texcoord_usage |= (1 << var->type->array_size()) - 1;
    163          this->lower_texcoord_array = false;
    164       }
    165       return visit_continue;
    166    }
    167 
    168    virtual ir_visitor_status visit(ir_variable *var)
    169    {
    170       if (var->data.mode != this->mode)
    171          return visit_continue;
    172 
    173       /* Nothing to do here for fragment outputs. */
    174       if (this->find_frag_outputs)
    175          return visit_continue;
    176 
    177       /* Handle colors and fog. */
    178       switch (var->data.location) {
    179       case VARYING_SLOT_COL0:
    180          this->color[0] = var;
    181          this->color_usage |= 1;
    182          break;
    183       case VARYING_SLOT_COL1:
    184          this->color[1] = var;
    185          this->color_usage |= 2;
    186          break;
    187       case VARYING_SLOT_BFC0:
    188          this->backcolor[0] = var;
    189          this->color_usage |= 1;
    190          break;
    191       case VARYING_SLOT_BFC1:
    192          this->backcolor[1] = var;
    193          this->color_usage |= 2;
    194          break;
    195       case VARYING_SLOT_FOGC:
    196          this->fog = var;
    197          this->has_fog = true;
    198          break;
    199       }
    200 
    201       return visit_continue;
    202    }
    203 
    204    void get(exec_list *ir,
    205             unsigned num_tfeedback_decls,
    206             tfeedback_decl *tfeedback_decls)
    207    {
    208       /* Handle the transform feedback varyings. */
    209       for (unsigned i = 0; i < num_tfeedback_decls; i++) {
    210          if (!tfeedback_decls[i].is_varying())
    211             continue;
    212 
    213          unsigned location = tfeedback_decls[i].get_location();
    214 
    215          switch (location) {
    216          case VARYING_SLOT_COL0:
    217          case VARYING_SLOT_BFC0:
    218             this->tfeedback_color_usage |= 1;
    219             break;
    220          case VARYING_SLOT_COL1:
    221          case VARYING_SLOT_BFC1:
    222             this->tfeedback_color_usage |= 2;
    223             break;
    224          case VARYING_SLOT_FOGC:
    225             this->tfeedback_has_fog = true;
    226             break;
    227          default:
    228             if (location >= VARYING_SLOT_TEX0 &&
    229                 location <= VARYING_SLOT_TEX7) {
    230                this->lower_texcoord_array = false;
    231             }
    232          }
    233       }
    234 
    235       /* Process the shader. */
    236       visit_list_elements(this, ir);
    237 
    238       if (!this->texcoord_array) {
    239          this->lower_texcoord_array = false;
    240       }
    241       if (!this->fragdata_array) {
    242          this->lower_fragdata_array = false;
    243       }
    244    }
    245 
    246    bool lower_texcoord_array;
    247    ir_variable *texcoord_array;
    248    unsigned texcoord_usage; /* bitmask */
    249 
    250    bool find_frag_outputs; /* false if it's looking for varyings */
    251    bool lower_fragdata_array;
    252    ir_variable *fragdata_array;
    253    unsigned fragdata_usage; /* bitmask */
    254 
    255    ir_variable *color[2];
    256    ir_variable *backcolor[2];
    257    unsigned color_usage; /* bitmask */
    258    unsigned tfeedback_color_usage; /* bitmask */
    259 
    260    ir_variable *fog;
    261    bool has_fog;
    262    bool tfeedback_has_fog;
    263 
    264    ir_variable_mode mode;
    265 };
    266 
    267 
    268 /**
    269  * This replaces unused varyings with temporary variables.
    270  *
    271  * If "ir" is the producer, the "external" usage should come from
    272  * the consumer. It also works the other way around. If either one is
    273  * missing, set the "external" usage to a full mask.
    274  */
    275 class replace_varyings_visitor : public ir_rvalue_visitor {
    276 public:
    277    replace_varyings_visitor(struct gl_linked_shader *sha,
    278                             const varying_info_visitor *info,
    279                             unsigned external_texcoord_usage,
    280                             unsigned external_color_usage,
    281                             bool external_has_fog)
    282       : shader(sha), info(info), new_fog(NULL)
    283    {
    284       void *const ctx = shader->ir;
    285 
    286       memset(this->new_fragdata, 0, sizeof(this->new_fragdata));
    287       memset(this->new_texcoord, 0, sizeof(this->new_texcoord));
    288       memset(this->new_color, 0, sizeof(this->new_color));
    289       memset(this->new_backcolor, 0, sizeof(this->new_backcolor));
    290 
    291       const char *mode_str =
    292          info->mode == ir_var_shader_in ? "in" : "out";
    293 
    294       /* Handle texcoord outputs.
    295        *
    296        * We're going to break down the gl_TexCoord array into separate
    297        * variables. First, add declarations of the new variables all
    298        * occurrences of gl_TexCoord will be replaced with.
    299        */
    300       if (info->lower_texcoord_array) {
    301          prepare_array(shader->ir, this->new_texcoord,
    302                        ARRAY_SIZE(this->new_texcoord),
    303                        VARYING_SLOT_TEX0, "TexCoord", mode_str,
    304                        info->texcoord_usage, external_texcoord_usage);
    305       }
    306 
    307       /* Handle gl_FragData in the same way like gl_TexCoord. */
    308       if (info->lower_fragdata_array) {
    309          prepare_array(shader->ir, this->new_fragdata,
    310                        ARRAY_SIZE(this->new_fragdata),
    311                        FRAG_RESULT_DATA0, "FragData", mode_str,
    312                        info->fragdata_usage, (1 << MAX_DRAW_BUFFERS) - 1);
    313       }
    314 
    315       /* Create dummy variables which will replace set-but-unused color and
    316        * fog outputs.
    317        */
    318       external_color_usage |= info->tfeedback_color_usage;
    319 
    320       for (int i = 0; i < 2; i++) {
    321          char name[32];
    322 
    323          if (!(external_color_usage & (1 << i))) {
    324             if (info->color[i]) {
    325                snprintf(name, 32, "gl_%s_FrontColor%i_dummy", mode_str, i);
    326                this->new_color[i] =
    327                   new (ctx) ir_variable(glsl_type::vec4_type, name,
    328                                         ir_var_temporary);
    329             }
    330 
    331             if (info->backcolor[i]) {
    332                snprintf(name, 32, "gl_%s_BackColor%i_dummy", mode_str, i);
    333                this->new_backcolor[i] =
    334                   new (ctx) ir_variable(glsl_type::vec4_type, name,
    335                                         ir_var_temporary);
    336             }
    337          }
    338       }
    339 
    340       if (!external_has_fog && !info->tfeedback_has_fog &&
    341           info->fog) {
    342          char name[32];
    343 
    344          snprintf(name, 32, "gl_%s_FogFragCoord_dummy", mode_str);
    345          this->new_fog = new (ctx) ir_variable(glsl_type::float_type, name,
    346                                                ir_var_temporary);
    347       }
    348 
    349       /* Now do the replacing. */
    350       visit_list_elements(this, shader->ir);
    351    }
    352 
    353    void prepare_array(exec_list *ir,
    354                       ir_variable **new_var,
    355                       int max_elements, unsigned start_location,
    356                       const char *var_name, const char *mode_str,
    357                       unsigned usage, unsigned external_usage)
    358    {
    359       void *const ctx = ir;
    360 
    361       for (int i = max_elements-1; i >= 0; i--) {
    362          if (usage & (1 << i)) {
    363             char name[32];
    364 
    365             if (!(external_usage & (1 << i))) {
    366                /* This varying is unused in the next stage. Declare
    367                 * a temporary instead of an output. */
    368                snprintf(name, 32, "gl_%s_%s%i_dummy", mode_str, var_name, i);
    369                new_var[i] =
    370                   new (ctx) ir_variable(glsl_type::vec4_type, name,
    371                                         ir_var_temporary);
    372             }
    373             else {
    374                snprintf(name, 32, "gl_%s_%s%i", mode_str, var_name, i);
    375                new_var[i] =
    376                   new(ctx) ir_variable(glsl_type::vec4_type, name,
    377                                        this->info->mode);
    378                new_var[i]->data.location = start_location + i;
    379                new_var[i]->data.explicit_location = true;
    380                new_var[i]->data.explicit_index = 0;
    381             }
    382 
    383             ir->get_head_raw()->insert_before(new_var[i]);
    384          }
    385       }
    386    }
    387 
    388    virtual ir_visitor_status visit(ir_variable *var)
    389    {
    390       /* Remove the gl_TexCoord array. */
    391       if (this->info->lower_texcoord_array &&
    392           var == this->info->texcoord_array) {
    393          var->remove();
    394       }
    395 
    396       /* Remove the gl_FragData array. */
    397       if (this->info->lower_fragdata_array &&
    398           var == this->info->fragdata_array) {
    399 
    400          /* Clone variable for program resource list before it is removed. */
    401          if (!shader->fragdata_arrays)
    402             shader->fragdata_arrays = new (shader) exec_list;
    403 
    404          shader->fragdata_arrays->push_tail(var->clone(shader, NULL));
    405 
    406          var->remove();
    407       }
    408 
    409       /* Replace set-but-unused color and fog outputs with dummy variables. */
    410       for (int i = 0; i < 2; i++) {
    411          if (var == this->info->color[i] && this->new_color[i]) {
    412             var->replace_with(this->new_color[i]);
    413          }
    414          if (var == this->info->backcolor[i] &&
    415              this->new_backcolor[i]) {
    416             var->replace_with(this->new_backcolor[i]);
    417          }
    418       }
    419 
    420       if (var == this->info->fog && this->new_fog) {
    421          var->replace_with(this->new_fog);
    422       }
    423 
    424       return visit_continue;
    425    }
    426 
    427    virtual void handle_rvalue(ir_rvalue **rvalue)
    428    {
    429       if (!*rvalue)
    430          return;
    431 
    432       void *ctx = ralloc_parent(*rvalue);
    433 
    434       /* Replace an array dereference gl_TexCoord[i] with a single
    435        * variable dereference representing gl_TexCoord[i].
    436        */
    437       if (this->info->lower_texcoord_array) {
    438          /* gl_TexCoord[i] occurrence */
    439          ir_dereference_array *const da = (*rvalue)->as_dereference_array();
    440 
    441          if (da && da->variable_referenced() ==
    442              this->info->texcoord_array) {
    443             unsigned i = da->array_index->as_constant()->get_uint_component(0);
    444 
    445             *rvalue = new(ctx) ir_dereference_variable(this->new_texcoord[i]);
    446             return;
    447          }
    448       }
    449 
    450       /* Same for gl_FragData. */
    451       if (this->info->lower_fragdata_array) {
    452          /* gl_FragData[i] occurrence */
    453          ir_dereference_array *const da = (*rvalue)->as_dereference_array();
    454 
    455          if (da && da->variable_referenced() == this->info->fragdata_array) {
    456             unsigned i = da->array_index->as_constant()->get_uint_component(0);
    457 
    458             *rvalue = new(ctx) ir_dereference_variable(this->new_fragdata[i]);
    459             return;
    460          }
    461       }
    462 
    463       /* Replace set-but-unused color and fog outputs with dummy variables. */
    464       ir_dereference_variable *const dv = (*rvalue)->as_dereference_variable();
    465       if (!dv)
    466          return;
    467 
    468       ir_variable *var = dv->variable_referenced();
    469 
    470       for (int i = 0; i < 2; i++) {
    471          if (var == this->info->color[i] && this->new_color[i]) {
    472             *rvalue = new(ctx) ir_dereference_variable(this->new_color[i]);
    473             return;
    474          }
    475          if (var == this->info->backcolor[i] &&
    476              this->new_backcolor[i]) {
    477             *rvalue = new(ctx) ir_dereference_variable(this->new_backcolor[i]);
    478             return;
    479          }
    480       }
    481 
    482       if (var == this->info->fog && this->new_fog) {
    483          *rvalue = new(ctx) ir_dereference_variable(this->new_fog);
    484       }
    485    }
    486 
    487    virtual ir_visitor_status visit_leave(ir_assignment *ir)
    488    {
    489       handle_rvalue(&ir->rhs);
    490       handle_rvalue(&ir->condition);
    491 
    492       /* We have to use set_lhs when changing the LHS of an assignment. */
    493       ir_rvalue *lhs = ir->lhs;
    494 
    495       handle_rvalue(&lhs);
    496       if (lhs != ir->lhs) {
    497          ir->set_lhs(lhs);
    498       }
    499 
    500       return visit_continue;
    501    }
    502 
    503 private:
    504    struct gl_linked_shader *shader;
    505    const varying_info_visitor *info;
    506    ir_variable *new_fragdata[MAX_DRAW_BUFFERS];
    507    ir_variable *new_texcoord[MAX_TEXTURE_COORD_UNITS];
    508    ir_variable *new_color[2];
    509    ir_variable *new_backcolor[2];
    510    ir_variable *new_fog;
    511 };
    512 
    513 } /* anonymous namespace */
    514 
    515 static void
    516 lower_texcoord_array(struct gl_linked_shader *shader, const varying_info_visitor *info)
    517 {
    518    replace_varyings_visitor(shader, info,
    519                             (1 << MAX_TEXTURE_COORD_UNITS) - 1,
    520                             1 | 2, true);
    521 }
    522 
    523 static void
    524 lower_fragdata_array(struct gl_linked_shader *shader)
    525 {
    526    varying_info_visitor info(ir_var_shader_out, true);
    527    info.get(shader->ir, 0, NULL);
    528 
    529    replace_varyings_visitor(shader, &info, 0, 0, 0);
    530 }
    531 
    532 
    533 void
    534 do_dead_builtin_varyings(struct gl_context *ctx,
    535                          gl_linked_shader *producer,
    536                          gl_linked_shader *consumer,
    537                          unsigned num_tfeedback_decls,
    538                          tfeedback_decl *tfeedback_decls)
    539 {
    540    /* Lower the gl_FragData array to separate variables. */
    541    if (consumer && consumer->Stage == MESA_SHADER_FRAGMENT) {
    542       lower_fragdata_array(consumer);
    543    }
    544 
    545    /* Lowering of built-in varyings has no effect with the core context and
    546     * GLES2, because they are not available there.
    547     */
    548    if (ctx->API == API_OPENGL_CORE ||
    549        ctx->API == API_OPENGLES2) {
    550       return;
    551    }
    552 
    553    /* Information about built-in varyings. */
    554    varying_info_visitor producer_info(ir_var_shader_out);
    555    varying_info_visitor consumer_info(ir_var_shader_in);
    556 
    557    if (producer) {
    558       producer_info.get(producer->ir, num_tfeedback_decls, tfeedback_decls);
    559 
    560       if (!consumer) {
    561          /* At least eliminate unused gl_TexCoord elements. */
    562          if (producer_info.lower_texcoord_array) {
    563             lower_texcoord_array(producer, &producer_info);
    564          }
    565          return;
    566       }
    567    }
    568 
    569    if (consumer) {
    570       consumer_info.get(consumer->ir, 0, NULL);
    571 
    572       if (!producer) {
    573          /* At least eliminate unused gl_TexCoord elements. */
    574          if (consumer_info.lower_texcoord_array) {
    575             lower_texcoord_array(consumer, &consumer_info);
    576          }
    577          return;
    578       }
    579    }
    580 
    581    /* Eliminate the outputs unused by the consumer. */
    582    if (producer_info.lower_texcoord_array ||
    583        producer_info.color_usage ||
    584        producer_info.has_fog) {
    585       replace_varyings_visitor(producer,
    586                                &producer_info,
    587                                consumer_info.texcoord_usage,
    588                                consumer_info.color_usage,
    589                                consumer_info.has_fog);
    590    }
    591 
    592    /* The gl_TexCoord fragment shader inputs can be initialized
    593     * by GL_COORD_REPLACE, so we can't eliminate them.
    594     *
    595     * This doesn't prevent elimination of the gl_TexCoord elements which
    596     * are not read by the fragment shader. We want to eliminate those anyway.
    597     */
    598    if (consumer->Stage == MESA_SHADER_FRAGMENT) {
    599       producer_info.texcoord_usage = (1 << MAX_TEXTURE_COORD_UNITS) - 1;
    600    }
    601 
    602    /* Eliminate the inputs uninitialized by the producer. */
    603    if (consumer_info.lower_texcoord_array ||
    604        consumer_info.color_usage ||
    605        consumer_info.has_fog) {
    606       replace_varyings_visitor(consumer,
    607                                &consumer_info,
    608                                producer_info.texcoord_usage,
    609                                producer_info.color_usage,
    610                                producer_info.has_fog);
    611    }
    612 }
    613