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
58namespace {
59
60/**
61 * This obtains detailed information about built-in varyings from shader code.
62 */
63class varying_info_visitor : public ir_hierarchical_visitor {
64public:
65   /* "mode" can be either ir_var_shader_in or ir_var_shader_out */
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
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
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
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
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 */
276class replace_varyings_visitor : public ir_rvalue_visitor {
277public:
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
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
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
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
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
504private:
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
516static void
517lower_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
524static void
525lower_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, false);
531}
532
533
534void
535do_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