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 "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 */
varying_info_visitor(ir_variable_mode mode,bool find_frag_outputs=false)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
visit_enter(ir_dereference_array * ir)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
visit(ir_dereference_variable * ir)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
visit(ir_variable * var)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
get(exec_list * ir,unsigned num_tfeedback_decls,tfeedback_decl * tfeedback_decls)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:
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)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
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)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
visit(ir_variable * var)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
handle_rvalue(ir_rvalue ** rvalue)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
visit_leave(ir_assignment * ir)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
lower_texcoord_array(struct gl_linked_shader * shader,const varying_info_visitor * info)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
lower_fragdata_array(struct gl_linked_shader * shader)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
do_dead_builtin_varyings(struct gl_context * ctx,gl_linked_shader * producer,gl_linked_shader * consumer,unsigned num_tfeedback_decls,tfeedback_decl * tfeedback_decls)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