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