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, false);
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