Details | Last modification | View Log | RSS feed
Rev | Author | Line No. | Line |
---|---|---|---|
4358 | Serge | 1 | /* |
2 | * Copyright © 2011 Intel Corporation |
||
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 lower_clip_distance.cpp |
||
26 | * |
||
27 | * This pass accounts for the difference between the way |
||
28 | * gl_ClipDistance is declared in standard GLSL (as an array of |
||
29 | * floats), and the way it is frequently implemented in hardware (as |
||
30 | * a pair of vec4s, with four clip distances packed into each). |
||
31 | * |
||
32 | * The declaration of gl_ClipDistance is replaced with a declaration |
||
33 | * of gl_ClipDistanceMESA, and any references to gl_ClipDistance are |
||
34 | * translated to refer to gl_ClipDistanceMESA with the appropriate |
||
35 | * swizzling of array indices. For instance: |
||
36 | * |
||
37 | * gl_ClipDistance[i] |
||
38 | * |
||
39 | * is translated into: |
||
40 | * |
||
41 | * gl_ClipDistanceMESA[i>>2][i&3] |
||
42 | * |
||
43 | * Since some hardware may not internally represent gl_ClipDistance as a pair |
||
44 | * of vec4's, this lowering pass is optional. To enable it, set the |
||
45 | * LowerClipDistance flag in gl_shader_compiler_options to true. |
||
46 | */ |
||
47 | |||
48 | #include "glsl_symbol_table.h" |
||
49 | #include "ir_rvalue_visitor.h" |
||
50 | #include "ir.h" |
||
51 | #include "program/prog_instruction.h" /* For WRITEMASK_* */ |
||
52 | |||
53 | class lower_clip_distance_visitor : public ir_rvalue_visitor { |
||
54 | public: |
||
55 | lower_clip_distance_visitor() |
||
56 | : progress(false), old_clip_distance_var(NULL), |
||
57 | new_clip_distance_var(NULL) |
||
58 | { |
||
59 | } |
||
60 | |||
61 | virtual ir_visitor_status visit(ir_variable *); |
||
62 | void create_indices(ir_rvalue*, ir_rvalue *&, ir_rvalue *&); |
||
63 | virtual ir_visitor_status visit_leave(ir_assignment *); |
||
64 | void visit_new_assignment(ir_assignment *ir); |
||
65 | virtual ir_visitor_status visit_leave(ir_call *); |
||
66 | |||
67 | virtual void handle_rvalue(ir_rvalue **rvalue); |
||
68 | |||
69 | void fix_lhs(ir_assignment *); |
||
70 | |||
71 | bool progress; |
||
72 | |||
73 | /** |
||
74 | * Pointer to the declaration of gl_ClipDistance, if found. |
||
75 | */ |
||
76 | ir_variable *old_clip_distance_var; |
||
77 | |||
78 | /** |
||
79 | * Pointer to the newly-created gl_ClipDistanceMESA variable. |
||
80 | */ |
||
81 | ir_variable *new_clip_distance_var; |
||
82 | }; |
||
83 | |||
84 | |||
85 | /** |
||
86 | * Replace any declaration of gl_ClipDistance as an array of floats with a |
||
87 | * declaration of gl_ClipDistanceMESA as an array of vec4's. |
||
88 | */ |
||
89 | ir_visitor_status |
||
90 | lower_clip_distance_visitor::visit(ir_variable *ir) |
||
91 | { |
||
92 | /* No point in looking for the declaration of gl_ClipDistance if |
||
93 | * we've already found it. |
||
94 | */ |
||
95 | if (this->old_clip_distance_var) |
||
96 | return visit_continue; |
||
97 | |||
98 | if (ir->name && strcmp(ir->name, "gl_ClipDistance") == 0) { |
||
99 | this->progress = true; |
||
100 | this->old_clip_distance_var = ir; |
||
101 | assert (ir->type->is_array()); |
||
102 | assert (ir->type->element_type() == glsl_type::float_type); |
||
103 | unsigned new_size = (ir->type->array_size() + 3) / 4; |
||
104 | |||
105 | /* Clone the old var so that we inherit all of its properties */ |
||
106 | this->new_clip_distance_var = ir->clone(ralloc_parent(ir), NULL); |
||
107 | |||
108 | /* And change the properties that we need to change */ |
||
109 | this->new_clip_distance_var->name |
||
110 | = ralloc_strdup(this->new_clip_distance_var, "gl_ClipDistanceMESA"); |
||
111 | this->new_clip_distance_var->type |
||
112 | = glsl_type::get_array_instance(glsl_type::vec4_type, new_size); |
||
113 | this->new_clip_distance_var->max_array_access = ir->max_array_access / 4; |
||
114 | |||
115 | ir->replace_with(this->new_clip_distance_var); |
||
116 | } |
||
117 | return visit_continue; |
||
118 | } |
||
119 | |||
120 | |||
121 | /** |
||
122 | * Create the necessary GLSL rvalues to index into gl_ClipDistanceMESA based |
||
123 | * on the rvalue previously used to index into gl_ClipDistance. |
||
124 | * |
||
125 | * \param array_index Selects one of the vec4's in gl_ClipDistanceMESA |
||
126 | * \param swizzle_index Selects a component within the vec4 selected by |
||
127 | * array_index. |
||
128 | */ |
||
129 | void |
||
130 | lower_clip_distance_visitor::create_indices(ir_rvalue *old_index, |
||
131 | ir_rvalue *&array_index, |
||
132 | ir_rvalue *&swizzle_index) |
||
133 | { |
||
134 | void *ctx = ralloc_parent(old_index); |
||
135 | |||
136 | /* Make sure old_index is a signed int so that the bitwise "shift" and |
||
137 | * "and" operations below type check properly. |
||
138 | */ |
||
139 | if (old_index->type != glsl_type::int_type) { |
||
140 | assert (old_index->type == glsl_type::uint_type); |
||
141 | old_index = new(ctx) ir_expression(ir_unop_u2i, old_index); |
||
142 | } |
||
143 | |||
144 | ir_constant *old_index_constant = old_index->constant_expression_value(); |
||
145 | if (old_index_constant) { |
||
146 | /* gl_ClipDistance is being accessed via a constant index. Don't bother |
||
147 | * creating expressions to calculate the lowered indices. Just create |
||
148 | * constants. |
||
149 | */ |
||
150 | int const_val = old_index_constant->get_int_component(0); |
||
151 | array_index = new(ctx) ir_constant(const_val / 4); |
||
152 | swizzle_index = new(ctx) ir_constant(const_val % 4); |
||
153 | } else { |
||
154 | /* Create a variable to hold the value of old_index (so that we |
||
155 | * don't compute it twice). |
||
156 | */ |
||
157 | ir_variable *old_index_var = new(ctx) ir_variable( |
||
158 | glsl_type::int_type, "clip_distance_index", ir_var_temporary); |
||
159 | this->base_ir->insert_before(old_index_var); |
||
160 | this->base_ir->insert_before(new(ctx) ir_assignment( |
||
161 | new(ctx) ir_dereference_variable(old_index_var), old_index)); |
||
162 | |||
163 | /* Create the expression clip_distance_index / 4. Do this as a bit |
||
164 | * shift because that's likely to be more efficient. |
||
165 | */ |
||
166 | array_index = new(ctx) ir_expression( |
||
167 | ir_binop_rshift, new(ctx) ir_dereference_variable(old_index_var), |
||
168 | new(ctx) ir_constant(2)); |
||
169 | |||
170 | /* Create the expression clip_distance_index % 4. Do this as a bitwise |
||
171 | * AND because that's likely to be more efficient. |
||
172 | */ |
||
173 | swizzle_index = new(ctx) ir_expression( |
||
174 | ir_binop_bit_and, new(ctx) ir_dereference_variable(old_index_var), |
||
175 | new(ctx) ir_constant(3)); |
||
176 | } |
||
177 | } |
||
178 | |||
179 | |||
180 | void |
||
181 | lower_clip_distance_visitor::handle_rvalue(ir_rvalue **rv) |
||
182 | { |
||
183 | /* If the gl_ClipDistance var hasn't been declared yet, then |
||
184 | * there's no way this deref can refer to it. |
||
185 | */ |
||
186 | if (!this->old_clip_distance_var || *rv == NULL) |
||
187 | return; |
||
188 | |||
189 | ir_dereference_array *const array_deref = (*rv)->as_dereference_array(); |
||
190 | if (array_deref == NULL) |
||
191 | return; |
||
192 | |||
193 | /* Replace any expression that indexes into the gl_ClipDistance array |
||
194 | * with an expression that indexes into one of the vec4's in |
||
195 | * gl_ClipDistanceMESA and accesses the appropriate component. |
||
196 | */ |
||
197 | ir_dereference_variable *old_var_ref = |
||
198 | array_deref->array->as_dereference_variable(); |
||
199 | if (old_var_ref && old_var_ref->var == this->old_clip_distance_var) { |
||
200 | this->progress = true; |
||
201 | ir_rvalue *array_index; |
||
202 | ir_rvalue *swizzle_index; |
||
203 | this->create_indices(array_deref->array_index, array_index, swizzle_index); |
||
204 | void *mem_ctx = ralloc_parent(array_deref); |
||
205 | |||
206 | ir_dereference_array *const ClipDistanceMESA_deref = |
||
207 | new(mem_ctx) ir_dereference_array(this->new_clip_distance_var, |
||
208 | array_index); |
||
209 | |||
210 | ir_expression *const expr = |
||
211 | new(mem_ctx) ir_expression(ir_binop_vector_extract, |
||
212 | ClipDistanceMESA_deref, |
||
213 | swizzle_index); |
||
214 | |||
215 | *rv = expr; |
||
216 | } |
||
217 | } |
||
218 | |||
219 | void |
||
220 | lower_clip_distance_visitor::fix_lhs(ir_assignment *ir) |
||
221 | { |
||
222 | if (ir->lhs->ir_type == ir_type_expression) { |
||
223 | void *mem_ctx = ralloc_parent(ir); |
||
224 | ir_expression *const expr = (ir_expression *) ir->lhs; |
||
225 | |||
226 | /* The expression must be of the form: |
||
227 | * |
||
228 | * (vector_extract gl_ClipDistanceMESA[i], j). |
||
229 | */ |
||
230 | assert(expr->operation == ir_binop_vector_extract); |
||
231 | assert(expr->operands[0]->ir_type == ir_type_dereference_array); |
||
232 | assert(expr->operands[0]->type == glsl_type::vec4_type); |
||
233 | |||
234 | ir_dereference *const new_lhs = (ir_dereference *) expr->operands[0]; |
||
235 | ir->rhs = new(mem_ctx) ir_expression(ir_triop_vector_insert, |
||
236 | glsl_type::vec4_type, |
||
237 | new_lhs->clone(mem_ctx, NULL), |
||
238 | ir->rhs, |
||
239 | expr->operands[1]); |
||
240 | ir->set_lhs(new_lhs); |
||
241 | ir->write_mask = WRITEMASK_XYZW; |
||
242 | } |
||
243 | } |
||
244 | |||
245 | /** |
||
246 | * Replace any assignment having gl_ClipDistance (undereferenced) as its LHS |
||
247 | * or RHS with a sequence of assignments, one for each component of the array. |
||
248 | * Each of these assignments is lowered to refer to gl_ClipDistanceMESA as |
||
249 | * appropriate. |
||
250 | */ |
||
251 | ir_visitor_status |
||
252 | lower_clip_distance_visitor::visit_leave(ir_assignment *ir) |
||
253 | { |
||
4401 | Serge | 254 | /* First invoke the base class visitor. This causes handle_rvalue() to be |
255 | * called on ir->rhs and ir->condition. |
||
256 | */ |
||
257 | ir_rvalue_visitor::visit_leave(ir); |
||
258 | |||
4358 | Serge | 259 | ir_dereference_variable *lhs_var = ir->lhs->as_dereference_variable(); |
260 | ir_dereference_variable *rhs_var = ir->rhs->as_dereference_variable(); |
||
261 | if ((lhs_var && lhs_var->var == this->old_clip_distance_var) |
||
262 | || (rhs_var && rhs_var->var == this->old_clip_distance_var)) { |
||
263 | /* LHS or RHS of the assignment is the entire gl_ClipDistance array. |
||
264 | * Since we are reshaping gl_ClipDistance from an array of floats to an |
||
265 | * array of vec4's, this isn't going to work as a bulk assignment |
||
266 | * anymore, so unroll it to element-by-element assignments and lower |
||
267 | * each of them. |
||
268 | * |
||
269 | * Note: to unroll into element-by-element assignments, we need to make |
||
270 | * clones of the LHS and RHS. This is safe because expressions and |
||
271 | * l-values are side-effect free. |
||
272 | */ |
||
273 | void *ctx = ralloc_parent(ir); |
||
274 | int array_size = this->old_clip_distance_var->type->array_size(); |
||
275 | for (int i = 0; i < array_size; ++i) { |
||
276 | ir_dereference_array *new_lhs = new(ctx) ir_dereference_array( |
||
277 | ir->lhs->clone(ctx, NULL), new(ctx) ir_constant(i)); |
||
278 | ir_dereference_array *new_rhs = new(ctx) ir_dereference_array( |
||
279 | ir->rhs->clone(ctx, NULL), new(ctx) ir_constant(i)); |
||
280 | this->handle_rvalue((ir_rvalue **) &new_rhs); |
||
281 | |||
282 | /* Handle the LHS after creating the new assignment. This must |
||
283 | * happen in this order because handle_rvalue may replace the old LHS |
||
284 | * with an ir_expression of ir_binop_vector_extract. Since this is |
||
285 | * not a valide l-value, this will cause an assertion in the |
||
286 | * ir_assignment constructor to fail. |
||
287 | * |
||
288 | * If this occurs, replace the mangled LHS with a dereference of the |
||
289 | * vector, and replace the RHS with an ir_triop_vector_insert. |
||
290 | */ |
||
291 | ir_assignment *const assign = new(ctx) ir_assignment(new_lhs, new_rhs); |
||
292 | this->handle_rvalue((ir_rvalue **) &assign->lhs); |
||
293 | this->fix_lhs(assign); |
||
294 | |||
295 | this->base_ir->insert_before(assign); |
||
296 | } |
||
297 | ir->remove(); |
||
298 | |||
299 | return visit_continue; |
||
300 | } |
||
301 | |||
302 | /* Handle the LHS as if it were an r-value. Normally |
||
303 | * rvalue_visit(ir_assignment *) only visits the RHS, but we need to lower |
||
304 | * expressions in the LHS as well. |
||
305 | * |
||
306 | * This may cause the LHS to get replaced with an ir_expression of |
||
307 | * ir_binop_vector_extract. If this occurs, replace it with a dereference |
||
308 | * of the vector, and replace the RHS with an ir_triop_vector_insert. |
||
309 | */ |
||
310 | handle_rvalue((ir_rvalue **)&ir->lhs); |
||
311 | this->fix_lhs(ir); |
||
312 | |||
313 | return rvalue_visit(ir); |
||
314 | } |
||
315 | |||
316 | |||
317 | /** |
||
318 | * Set up base_ir properly and call visit_leave() on a newly created |
||
319 | * ir_assignment node. This is used in cases where we have to insert an |
||
320 | * ir_assignment in a place where we know the hierarchical visitor won't see |
||
321 | * it. |
||
322 | */ |
||
323 | void |
||
324 | lower_clip_distance_visitor::visit_new_assignment(ir_assignment *ir) |
||
325 | { |
||
326 | ir_instruction *old_base_ir = this->base_ir; |
||
327 | this->base_ir = ir; |
||
328 | ir->accept(this); |
||
329 | this->base_ir = old_base_ir; |
||
330 | } |
||
331 | |||
332 | |||
333 | /** |
||
334 | * If gl_ClipDistance appears as an argument in an ir_call expression, replace |
||
335 | * it with a temporary variable, and make sure the ir_call is preceded and/or |
||
336 | * followed by assignments that copy the contents of the temporary variable to |
||
337 | * and/or from gl_ClipDistance. Each of these assignments is then lowered to |
||
338 | * refer to gl_ClipDistanceMESA. |
||
339 | */ |
||
340 | ir_visitor_status |
||
341 | lower_clip_distance_visitor::visit_leave(ir_call *ir) |
||
342 | { |
||
343 | void *ctx = ralloc_parent(ir); |
||
344 | |||
345 | const exec_node *formal_param_node = ir->callee->parameters.head; |
||
346 | const exec_node *actual_param_node = ir->actual_parameters.head; |
||
347 | while (!actual_param_node->is_tail_sentinel()) { |
||
348 | ir_variable *formal_param = (ir_variable *) formal_param_node; |
||
349 | ir_rvalue *actual_param = (ir_rvalue *) actual_param_node; |
||
350 | |||
351 | /* Advance formal_param_node and actual_param_node now so that we can |
||
352 | * safely replace actual_param with another node, if necessary, below. |
||
353 | */ |
||
354 | formal_param_node = formal_param_node->next; |
||
355 | actual_param_node = actual_param_node->next; |
||
356 | |||
357 | ir_dereference_variable *deref = actual_param->as_dereference_variable(); |
||
358 | if (deref && deref->var == this->old_clip_distance_var) { |
||
359 | /* User is trying to pass the whole gl_ClipDistance array to a |
||
360 | * function call. Since we are reshaping gl_ClipDistance from an |
||
361 | * array of floats to an array of vec4's, this isn't going to work |
||
362 | * anymore, so use a temporary array instead. |
||
363 | */ |
||
364 | ir_variable *temp_clip_distance = new(ctx) ir_variable( |
||
365 | actual_param->type, "temp_clip_distance", ir_var_temporary); |
||
366 | this->base_ir->insert_before(temp_clip_distance); |
||
367 | actual_param->replace_with( |
||
368 | new(ctx) ir_dereference_variable(temp_clip_distance)); |
||
369 | if (formal_param->mode == ir_var_function_in |
||
370 | || formal_param->mode == ir_var_function_inout) { |
||
371 | /* Copy from gl_ClipDistance to the temporary before the call. |
||
372 | * Since we are going to insert this copy before the current |
||
373 | * instruction, we need to visit it afterwards to make sure it |
||
374 | * gets lowered. |
||
375 | */ |
||
376 | ir_assignment *new_assignment = new(ctx) ir_assignment( |
||
377 | new(ctx) ir_dereference_variable(temp_clip_distance), |
||
378 | new(ctx) ir_dereference_variable(old_clip_distance_var)); |
||
379 | this->base_ir->insert_before(new_assignment); |
||
380 | this->visit_new_assignment(new_assignment); |
||
381 | } |
||
382 | if (formal_param->mode == ir_var_function_out |
||
383 | || formal_param->mode == ir_var_function_inout) { |
||
384 | /* Copy from the temporary to gl_ClipDistance after the call. |
||
385 | * Since visit_list_elements() has already decided which |
||
386 | * instruction it's going to visit next, we need to visit |
||
387 | * afterwards to make sure it gets lowered. |
||
388 | */ |
||
389 | ir_assignment *new_assignment = new(ctx) ir_assignment( |
||
390 | new(ctx) ir_dereference_variable(old_clip_distance_var), |
||
391 | new(ctx) ir_dereference_variable(temp_clip_distance)); |
||
392 | this->base_ir->insert_after(new_assignment); |
||
393 | this->visit_new_assignment(new_assignment); |
||
394 | } |
||
395 | } |
||
396 | } |
||
397 | |||
398 | return rvalue_visit(ir); |
||
399 | } |
||
400 | |||
401 | |||
402 | bool |
||
403 | lower_clip_distance(gl_shader *shader) |
||
404 | { |
||
405 | lower_clip_distance_visitor v; |
||
406 | |||
407 | visit_list_elements(&v, shader->ir); |
||
408 | |||
409 | if (v.new_clip_distance_var) |
||
410 | shader->symbols->add_variable(v.new_clip_distance_var); |
||
411 | |||
412 | return v.progress; |
||
413 | }> |