Home | History | Annotate | Download | only in glsl
      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 "ir_hierarchical_visitor.h"
     49 #include "ir.h"
     50 
     51 class lower_clip_distance_visitor : public ir_hierarchical_visitor {
     52 public:
     53    lower_clip_distance_visitor()
     54       : progress(false), old_clip_distance_var(NULL),
     55         new_clip_distance_var(NULL)
     56    {
     57    }
     58 
     59    virtual ir_visitor_status visit(ir_variable *);
     60    void create_indices(ir_rvalue*, ir_rvalue *&, ir_rvalue *&);
     61    virtual ir_visitor_status visit_leave(ir_dereference_array *);
     62    virtual ir_visitor_status visit_leave(ir_assignment *);
     63    void visit_new_assignment(ir_assignment *ir);
     64    virtual ir_visitor_status visit_leave(ir_call *);
     65 
     66    bool progress;
     67 
     68    /**
     69     * Pointer to the declaration of gl_ClipDistance, if found.
     70     */
     71    ir_variable *old_clip_distance_var;
     72 
     73    /**
     74     * Pointer to the newly-created gl_ClipDistanceMESA variable.
     75     */
     76    ir_variable *new_clip_distance_var;
     77 };
     78 
     79 
     80 /**
     81  * Replace any declaration of gl_ClipDistance as an array of floats with a
     82  * declaration of gl_ClipDistanceMESA as an array of vec4's.
     83  */
     84 ir_visitor_status
     85 lower_clip_distance_visitor::visit(ir_variable *ir)
     86 {
     87    /* No point in looking for the declaration of gl_ClipDistance if
     88     * we've already found it.
     89     */
     90    if (this->old_clip_distance_var)
     91       return visit_continue;
     92 
     93    if (ir->name && strcmp(ir->name, "gl_ClipDistance") == 0) {
     94       this->progress = true;
     95       this->old_clip_distance_var = ir;
     96       assert (ir->type->is_array());
     97       assert (ir->type->element_type() == glsl_type::float_type);
     98       unsigned new_size = (ir->type->array_size() + 3) / 4;
     99 
    100       /* Clone the old var so that we inherit all of its properties */
    101       this->new_clip_distance_var = ir->clone(ralloc_parent(ir), NULL);
    102 
    103       /* And change the properties that we need to change */
    104       this->new_clip_distance_var->name
    105          = ralloc_strdup(this->new_clip_distance_var, "gl_ClipDistanceMESA");
    106       this->new_clip_distance_var->type
    107          = glsl_type::get_array_instance(glsl_type::vec4_type, new_size);
    108       this->new_clip_distance_var->max_array_access = ir->max_array_access / 4;
    109 
    110       ir->replace_with(this->new_clip_distance_var);
    111    }
    112    return visit_continue;
    113 }
    114 
    115 
    116 /**
    117  * Create the necessary GLSL rvalues to index into gl_ClipDistanceMESA based
    118  * on the rvalue previously used to index into gl_ClipDistance.
    119  *
    120  * \param array_index Selects one of the vec4's in gl_ClipDistanceMESA
    121  * \param swizzle_index Selects a component within the vec4 selected by
    122  *        array_index.
    123  */
    124 void
    125 lower_clip_distance_visitor::create_indices(ir_rvalue *old_index,
    126                                             ir_rvalue *&array_index,
    127                                             ir_rvalue *&swizzle_index)
    128 {
    129    void *ctx = ralloc_parent(old_index);
    130 
    131    /* Make sure old_index is a signed int so that the bitwise "shift" and
    132     * "and" operations below type check properly.
    133     */
    134    if (old_index->type != glsl_type::int_type) {
    135       assert (old_index->type == glsl_type::uint_type);
    136       old_index = new(ctx) ir_expression(ir_unop_u2i, old_index);
    137    }
    138 
    139    ir_constant *old_index_constant = old_index->constant_expression_value();
    140    if (old_index_constant) {
    141       /* gl_ClipDistance is being accessed via a constant index.  Don't bother
    142        * creating expressions to calculate the lowered indices.  Just create
    143        * constants.
    144        */
    145       int const_val = old_index_constant->get_int_component(0);
    146       array_index = new(ctx) ir_constant(const_val / 4);
    147       swizzle_index = new(ctx) ir_constant(const_val % 4);
    148    } else {
    149       /* Create a variable to hold the value of old_index (so that we
    150        * don't compute it twice).
    151        */
    152       ir_variable *old_index_var = new(ctx) ir_variable(
    153          glsl_type::int_type, "clip_distance_index", ir_var_temporary);
    154       this->base_ir->insert_before(old_index_var);
    155       this->base_ir->insert_before(new(ctx) ir_assignment(
    156          new(ctx) ir_dereference_variable(old_index_var), old_index));
    157 
    158       /* Create the expression clip_distance_index / 4.  Do this as a bit
    159        * shift because that's likely to be more efficient.
    160        */
    161       array_index = new(ctx) ir_expression(
    162          ir_binop_rshift, new(ctx) ir_dereference_variable(old_index_var),
    163          new(ctx) ir_constant(2));
    164 
    165       /* Create the expression clip_distance_index % 4.  Do this as a bitwise
    166        * AND because that's likely to be more efficient.
    167        */
    168       swizzle_index = new(ctx) ir_expression(
    169          ir_binop_bit_and, new(ctx) ir_dereference_variable(old_index_var),
    170          new(ctx) ir_constant(3));
    171    }
    172 }
    173 
    174 
    175 /**
    176  * Replace any expression that indexes into the gl_ClipDistance array with an
    177  * expression that indexes into one of the vec4's in gl_ClipDistanceMESA and
    178  * accesses the appropriate component.
    179  */
    180 ir_visitor_status
    181 lower_clip_distance_visitor::visit_leave(ir_dereference_array *ir)
    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)
    187       return visit_continue;
    188 
    189    ir_dereference_variable *old_var_ref = ir->array->as_dereference_variable();
    190    if (old_var_ref && old_var_ref->var == this->old_clip_distance_var) {
    191       this->progress = true;
    192       ir_rvalue *array_index;
    193       ir_rvalue *swizzle_index;
    194       this->create_indices(ir->array_index, array_index, swizzle_index);
    195       void *mem_ctx = ralloc_parent(ir);
    196       ir->array = new(mem_ctx) ir_dereference_array(
    197          this->new_clip_distance_var, array_index);
    198       ir->array_index = swizzle_index;
    199    }
    200 
    201    return visit_continue;
    202 }
    203 
    204 
    205 /**
    206  * Replace any assignment having gl_ClipDistance (undereferenced) as its LHS
    207  * or RHS with a sequence of assignments, one for each component of the array.
    208  * Each of these assignments is lowered to refer to gl_ClipDistanceMESA as
    209  * appropriate.
    210  */
    211 ir_visitor_status
    212 lower_clip_distance_visitor::visit_leave(ir_assignment *ir)
    213 {
    214    ir_dereference_variable *lhs_var = ir->lhs->as_dereference_variable();
    215    ir_dereference_variable *rhs_var = ir->rhs->as_dereference_variable();
    216    if ((lhs_var && lhs_var->var == this->old_clip_distance_var)
    217        || (rhs_var && rhs_var->var == this->old_clip_distance_var)) {
    218       /* LHS or RHS of the assignment is the entire gl_ClipDistance array.
    219        * Since we are reshaping gl_ClipDistance from an array of floats to an
    220        * array of vec4's, this isn't going to work as a bulk assignment
    221        * anymore, so unroll it to element-by-element assignments and lower
    222        * each of them.
    223        *
    224        * Note: to unroll into element-by-element assignments, we need to make
    225        * clones of the LHS and RHS.  This is only safe if the LHS and RHS are
    226        * side-effect free.  Fortunately, we know that they are, because the
    227        * only kind of rvalue that can have side effects is an ir_call, and
    228        * ir_calls only appear (a) as a statement on their own, or (b) as the
    229        * RHS of an assignment that stores the result of the call in a
    230        * temporary variable.
    231        */
    232       void *ctx = ralloc_parent(ir);
    233       int array_size = this->old_clip_distance_var->type->array_size();
    234       for (int i = 0; i < array_size; ++i) {
    235          ir_dereference_array *new_lhs = new(ctx) ir_dereference_array(
    236             ir->lhs->clone(ctx, NULL), new(ctx) ir_constant(i));
    237          new_lhs->accept(this);
    238          ir_dereference_array *new_rhs = new(ctx) ir_dereference_array(
    239             ir->rhs->clone(ctx, NULL), new(ctx) ir_constant(i));
    240          new_rhs->accept(this);
    241          this->base_ir->insert_before(
    242             new(ctx) ir_assignment(new_lhs, new_rhs));
    243       }
    244       ir->remove();
    245    }
    246 
    247    return visit_continue;
    248 }
    249 
    250 
    251 /**
    252  * Set up base_ir properly and call visit_leave() on a newly created
    253  * ir_assignment node.  This is used in cases where we have to insert an
    254  * ir_assignment in a place where we know the hierarchical visitor won't see
    255  * it.
    256  */
    257 void
    258 lower_clip_distance_visitor::visit_new_assignment(ir_assignment *ir)
    259 {
    260    ir_instruction *old_base_ir = this->base_ir;
    261    this->base_ir = ir;
    262    ir->accept(this);
    263    this->base_ir = old_base_ir;
    264 }
    265 
    266 
    267 /**
    268  * If gl_ClipDistance appears as an argument in an ir_call expression, replace
    269  * it with a temporary variable, and make sure the ir_call is preceded and/or
    270  * followed by assignments that copy the contents of the temporary variable to
    271  * and/or from gl_ClipDistance.  Each of these assignments is then lowered to
    272  * refer to gl_ClipDistanceMESA.
    273  */
    274 ir_visitor_status
    275 lower_clip_distance_visitor::visit_leave(ir_call *ir)
    276 {
    277    void *ctx = ralloc_parent(ir);
    278 
    279    const exec_node *formal_param_node = ir->callee->parameters.head;
    280    const exec_node *actual_param_node = ir->actual_parameters.head;
    281    while (!actual_param_node->is_tail_sentinel()) {
    282       ir_variable *formal_param = (ir_variable *) formal_param_node;
    283       ir_rvalue *actual_param = (ir_rvalue *) actual_param_node;
    284 
    285       /* Advance formal_param_node and actual_param_node now so that we can
    286        * safely replace actual_param with another node, if necessary, below.
    287        */
    288       formal_param_node = formal_param_node->next;
    289       actual_param_node = actual_param_node->next;
    290 
    291       ir_dereference_variable *deref = actual_param->as_dereference_variable();
    292       if (deref && deref->var == this->old_clip_distance_var) {
    293          /* User is trying to pass the whole gl_ClipDistance array to a
    294           * function call.  Since we are reshaping gl_ClipDistance from an
    295           * array of floats to an array of vec4's, this isn't going to work
    296           * anymore, so use a temporary array instead.
    297           */
    298          ir_variable *temp_clip_distance = new(ctx) ir_variable(
    299             actual_param->type, "temp_clip_distance", ir_var_temporary);
    300          this->base_ir->insert_before(temp_clip_distance);
    301          actual_param->replace_with(
    302             new(ctx) ir_dereference_variable(temp_clip_distance));
    303          if (formal_param->mode == ir_var_in
    304              || formal_param->mode == ir_var_inout) {
    305             /* Copy from gl_ClipDistance to the temporary before the call.
    306              * Since we are going to insert this copy before the current
    307              * instruction, we need to visit it afterwards to make sure it
    308              * gets lowered.
    309              */
    310             ir_assignment *new_assignment = new(ctx) ir_assignment(
    311                new(ctx) ir_dereference_variable(temp_clip_distance),
    312                new(ctx) ir_dereference_variable(old_clip_distance_var));
    313             this->base_ir->insert_before(new_assignment);
    314             this->visit_new_assignment(new_assignment);
    315          }
    316          if (formal_param->mode == ir_var_out
    317              || formal_param->mode == ir_var_inout) {
    318             /* Copy from the temporary to gl_ClipDistance after the call.
    319              * Since visit_list_elements() has already decided which
    320              * instruction it's going to visit next, we need to visit
    321              * afterwards to make sure it gets lowered.
    322              */
    323             ir_assignment *new_assignment = new(ctx) ir_assignment(
    324                new(ctx) ir_dereference_variable(old_clip_distance_var),
    325                new(ctx) ir_dereference_variable(temp_clip_distance));
    326             this->base_ir->insert_after(new_assignment);
    327             this->visit_new_assignment(new_assignment);
    328          }
    329       }
    330    }
    331 
    332    return visit_continue;
    333 }
    334 
    335 
    336 bool
    337 lower_clip_distance(exec_list *instructions)
    338 {
    339    lower_clip_distance_visitor v;
    340 
    341    visit_list_elements(&v, instructions);
    342 
    343    return v.progress;
    344 }
    345