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