class lower_clip_distance_visitor : public ir_rvalue_visitor {
public:
- lower_clip_distance_visitor()
- : progress(false), old_clip_distance_var(NULL),
- new_clip_distance_var(NULL)
+ explicit lower_clip_distance_visitor(gl_shader_stage shader_stage)
+ : progress(false), old_clip_distance_out_var(NULL),
+ old_clip_distance_in_var(NULL), new_clip_distance_out_var(NULL),
+ new_clip_distance_in_var(NULL), shader_stage(shader_stage)
{
}
virtual ir_visitor_status visit(ir_variable *);
void create_indices(ir_rvalue*, ir_rvalue *&, ir_rvalue *&);
+ bool is_clip_distance_vec8(ir_rvalue *ir);
+ ir_rvalue *lower_clip_distance_vec8(ir_rvalue *ir);
virtual ir_visitor_status visit_leave(ir_assignment *);
void visit_new_assignment(ir_assignment *ir);
virtual ir_visitor_status visit_leave(ir_call *);
/**
* Pointer to the declaration of gl_ClipDistance, if found.
+ *
+ * Note:
+ *
+ * - the in_var is for geometry and both tessellation shader inputs only.
+ *
+ * - since gl_ClipDistance is available in tessellation control,
+ * tessellation evaluation and geometry shaders as both an input
+ * and an output, it's possible for both old_clip_distance_out_var
+ * and old_clip_distance_in_var to be non-null.
*/
- ir_variable *old_clip_distance_var;
+ ir_variable *old_clip_distance_out_var;
+ ir_variable *old_clip_distance_in_var;
/**
* Pointer to the newly-created gl_ClipDistanceMESA variable.
*/
- ir_variable *new_clip_distance_var;
+ ir_variable *new_clip_distance_out_var;
+ ir_variable *new_clip_distance_in_var;
+
+ /**
+ * Type of shader we are compiling (e.g. MESA_SHADER_VERTEX)
+ */
+ const gl_shader_stage shader_stage;
};
} /* anonymous namespace */
ir_visitor_status
lower_clip_distance_visitor::visit(ir_variable *ir)
{
- /* No point in looking for the declaration of gl_ClipDistance if
- * we've already found it.
- */
- if (this->old_clip_distance_var)
+ ir_variable **old_var;
+ ir_variable **new_var;
+
+ if (!ir->name || strcmp(ir->name, "gl_ClipDistance") != 0)
return visit_continue;
+ assert (ir->type->is_array());
+
+ if (ir->data.mode == ir_var_shader_out) {
+ if (this->old_clip_distance_out_var)
+ return visit_continue;
+ old_var = &old_clip_distance_out_var;
+ new_var = &new_clip_distance_out_var;
+ } else if (ir->data.mode == ir_var_shader_in) {
+ if (this->old_clip_distance_in_var)
+ return visit_continue;
+ old_var = &old_clip_distance_in_var;
+ new_var = &new_clip_distance_in_var;
+ } else {
+ unreachable("not reached");
+ }
- if (ir->name && strcmp(ir->name, "gl_ClipDistance") == 0) {
- this->progress = true;
- this->old_clip_distance_var = ir;
- assert (ir->type->is_array());
- assert (ir->type->element_type() == glsl_type::float_type);
+ this->progress = true;
+
+ if (!ir->type->fields.array->is_array()) {
+ /* gl_ClipDistance (used for vertex, tessellation evaluation and
+ * geometry output, and fragment input).
+ */
+ assert((ir->data.mode == ir_var_shader_in &&
+ this->shader_stage == MESA_SHADER_FRAGMENT) ||
+ (ir->data.mode == ir_var_shader_out &&
+ (this->shader_stage == MESA_SHADER_VERTEX ||
+ this->shader_stage == MESA_SHADER_TESS_EVAL ||
+ this->shader_stage == MESA_SHADER_GEOMETRY)));
+
+ *old_var = ir;
+ assert (ir->type->fields.array == glsl_type::float_type);
unsigned new_size = (ir->type->array_size() + 3) / 4;
/* Clone the old var so that we inherit all of its properties */
- this->new_clip_distance_var = ir->clone(ralloc_parent(ir), NULL);
+ *new_var = ir->clone(ralloc_parent(ir), NULL);
/* And change the properties that we need to change */
- this->new_clip_distance_var->name
- = ralloc_strdup(this->new_clip_distance_var, "gl_ClipDistanceMESA");
- this->new_clip_distance_var->type
- = glsl_type::get_array_instance(glsl_type::vec4_type, new_size);
- this->new_clip_distance_var->max_array_access = ir->max_array_access / 4;
+ (*new_var)->name = ralloc_strdup(*new_var, "gl_ClipDistanceMESA");
+ (*new_var)->type = glsl_type::get_array_instance(glsl_type::vec4_type,
+ new_size);
+ (*new_var)->data.max_array_access = ir->data.max_array_access / 4;
- ir->replace_with(this->new_clip_distance_var);
+ ir->replace_with(*new_var);
+ } else {
+ /* 2D gl_ClipDistance (used for tessellation control, tessellation
+ * evaluation and geometry input, and tessellation control output).
+ */
+ assert((ir->data.mode == ir_var_shader_in &&
+ (this->shader_stage == MESA_SHADER_GEOMETRY ||
+ this->shader_stage == MESA_SHADER_TESS_EVAL)) ||
+ this->shader_stage == MESA_SHADER_TESS_CTRL);
+
+ *old_var = ir;
+ assert (ir->type->fields.array->fields.array == glsl_type::float_type);
+ unsigned new_size = (ir->type->fields.array->array_size() + 3) / 4;
+
+ /* Clone the old var so that we inherit all of its properties */
+ *new_var = ir->clone(ralloc_parent(ir), NULL);
+
+ /* And change the properties that we need to change */
+ (*new_var)->name = ralloc_strdup(*new_var, "gl_ClipDistanceMESA");
+ (*new_var)->type = glsl_type::get_array_instance(
+ glsl_type::get_array_instance(glsl_type::vec4_type,
+ new_size),
+ ir->type->array_size());
+ (*new_var)->data.max_array_access = ir->data.max_array_access / 4;
+
+ ir->replace_with(*new_var);
}
+
return visit_continue;
}
}
+/**
+ * Determine whether the given rvalue describes an array of 8 floats that
+ * needs to be lowered to an array of 2 vec4's; that is, determine whether it
+ * matches one of the following patterns:
+ *
+ * - gl_ClipDistance (if gl_ClipDistance is 1D)
+ * - gl_ClipDistance[i] (if gl_ClipDistance is 2D)
+ */
+bool
+lower_clip_distance_visitor::is_clip_distance_vec8(ir_rvalue *ir)
+{
+ /* Note that geometry shaders contain gl_ClipDistance both as an input
+ * (which is a 2D array) and an output (which is a 1D array), so it's
+ * possible for both this->old_clip_distance_out_var and
+ * this->old_clip_distance_in_var to be non-NULL in the same shader.
+ */
+
+ if (!ir->type->is_array())
+ return false;
+ if (ir->type->fields.array != glsl_type::float_type)
+ return false;
+
+ if (this->old_clip_distance_out_var) {
+ if (ir->variable_referenced() == this->old_clip_distance_out_var)
+ return true;
+ }
+ if (this->old_clip_distance_in_var) {
+ assert(this->shader_stage == MESA_SHADER_TESS_CTRL ||
+ this->shader_stage == MESA_SHADER_TESS_EVAL ||
+ this->shader_stage == MESA_SHADER_GEOMETRY ||
+ this->shader_stage == MESA_SHADER_FRAGMENT);
+
+ if (ir->variable_referenced() == this->old_clip_distance_in_var)
+ return true;
+ }
+ return false;
+}
+
+
+/**
+ * If the given ir satisfies is_clip_distance_vec8(), return new ir
+ * representing its lowered equivalent. That is, map:
+ *
+ * - gl_ClipDistance => gl_ClipDistanceMESA (if gl_ClipDistance is 1D)
+ * - gl_ClipDistance[i] => gl_ClipDistanceMESA[i] (if gl_ClipDistance is 2D)
+ *
+ * Otherwise return NULL.
+ */
+ir_rvalue *
+lower_clip_distance_visitor::lower_clip_distance_vec8(ir_rvalue *ir)
+{
+ if (!ir->type->is_array())
+ return NULL;
+ if (ir->type->fields.array != glsl_type::float_type)
+ return NULL;
+
+ ir_variable **new_var = NULL;
+ if (this->old_clip_distance_out_var) {
+ if (ir->variable_referenced() == this->old_clip_distance_out_var)
+ new_var = &this->new_clip_distance_out_var;
+ }
+ if (this->old_clip_distance_in_var) {
+ if (ir->variable_referenced() == this->old_clip_distance_in_var)
+ new_var = &this->new_clip_distance_in_var;
+ }
+ if (new_var == NULL)
+ return NULL;
+
+ if (ir->as_dereference_variable()) {
+ return new(ralloc_parent(ir)) ir_dereference_variable(*new_var);
+ } else {
+ ir_dereference_array *array_ref = ir->as_dereference_array();
+ assert(array_ref);
+ assert(array_ref->array->as_dereference_variable());
+
+ return new(ralloc_parent(ir))
+ ir_dereference_array(*new_var, array_ref->array_index);
+ }
+}
+
+
void
lower_clip_distance_visitor::handle_rvalue(ir_rvalue **rv)
{
- /* If the gl_ClipDistance var hasn't been declared yet, then
- * there's no way this deref can refer to it.
- */
- if (!this->old_clip_distance_var || *rv == NULL)
+ if (*rv == NULL)
return;
ir_dereference_array *const array_deref = (*rv)->as_dereference_array();
if (array_deref == NULL)
return;
- /* Replace any expression that indexes into the gl_ClipDistance array
+ /* Replace any expression that indexes one of the floats in gl_ClipDistance
* with an expression that indexes into one of the vec4's in
* gl_ClipDistanceMESA and accesses the appropriate component.
*/
- ir_dereference_variable *old_var_ref =
- array_deref->array->as_dereference_variable();
- if (old_var_ref && old_var_ref->var == this->old_clip_distance_var) {
+ ir_rvalue *lowered_vec8 =
+ this->lower_clip_distance_vec8(array_deref->array);
+ if (lowered_vec8 != NULL) {
this->progress = true;
ir_rvalue *array_index;
ir_rvalue *swizzle_index;
this->create_indices(array_deref->array_index, array_index, swizzle_index);
void *mem_ctx = ralloc_parent(array_deref);
- ir_dereference_array *const ClipDistanceMESA_deref =
- new(mem_ctx) ir_dereference_array(this->new_clip_distance_var,
- array_index);
+ ir_dereference_array *const new_array_deref =
+ new(mem_ctx) ir_dereference_array(lowered_vec8, array_index);
ir_expression *const expr =
new(mem_ctx) ir_expression(ir_binop_vector_extract,
- ClipDistanceMESA_deref,
+ new_array_deref,
swizzle_index);
*rv = expr;
}
/**
- * Replace any assignment having gl_ClipDistance (undereferenced) as its LHS
- * or RHS with a sequence of assignments, one for each component of the array.
- * Each of these assignments is lowered to refer to gl_ClipDistanceMESA as
- * appropriate.
+ * Replace any assignment having the 1D gl_ClipDistance (undereferenced) as
+ * its LHS or RHS with a sequence of assignments, one for each component of
+ * the array. Each of these assignments is lowered to refer to
+ * gl_ClipDistanceMESA as appropriate.
+ *
+ * We need to do a similar replacement for 2D gl_ClipDistance, however since
+ * it's an input, the only case we need to address is where a 1D slice of it
+ * is the entire RHS of an assignment, e.g.:
+ *
+ * foo = gl_in[i].gl_ClipDistance
*/
ir_visitor_status
lower_clip_distance_visitor::visit_leave(ir_assignment *ir)
{
- ir_dereference_variable *lhs_var = ir->lhs->as_dereference_variable();
- ir_dereference_variable *rhs_var = ir->rhs->as_dereference_variable();
- if ((lhs_var && lhs_var->var == this->old_clip_distance_var)
- || (rhs_var && rhs_var->var == this->old_clip_distance_var)) {
- /* LHS or RHS of the assignment is the entire gl_ClipDistance array.
- * Since we are reshaping gl_ClipDistance from an array of floats to an
- * array of vec4's, this isn't going to work as a bulk assignment
- * anymore, so unroll it to element-by-element assignments and lower
- * each of them.
+ /* First invoke the base class visitor. This causes handle_rvalue() to be
+ * called on ir->rhs and ir->condition.
+ */
+ ir_rvalue_visitor::visit_leave(ir);
+
+ if (this->is_clip_distance_vec8(ir->lhs) ||
+ this->is_clip_distance_vec8(ir->rhs)) {
+ /* LHS or RHS of the assignment is the entire 1D gl_ClipDistance array
+ * (or a 1D slice of a 2D gl_ClipDistance input array). Since we are
+ * reshaping gl_ClipDistance from an array of floats to an array of
+ * vec4's, this isn't going to work as a bulk assignment anymore, so
+ * unroll it to element-by-element assignments and lower each of them.
*
* Note: to unroll into element-by-element assignments, we need to make
* clones of the LHS and RHS. This is safe because expressions and
* l-values are side-effect free.
*/
void *ctx = ralloc_parent(ir);
- int array_size = this->old_clip_distance_var->type->array_size();
+ int array_size = ir->lhs->type->array_size();
for (int i = 0; i < array_size; ++i) {
ir_dereference_array *new_lhs = new(ctx) ir_dereference_array(
ir->lhs->clone(ctx, NULL), new(ctx) ir_constant(i));
/**
- * If gl_ClipDistance appears as an argument in an ir_call expression, replace
- * it with a temporary variable, and make sure the ir_call is preceded and/or
- * followed by assignments that copy the contents of the temporary variable to
- * and/or from gl_ClipDistance. Each of these assignments is then lowered to
- * refer to gl_ClipDistanceMESA.
+ * If a 1D gl_ClipDistance variable appears as an argument in an ir_call
+ * expression, replace it with a temporary variable, and make sure the ir_call
+ * is preceded and/or followed by assignments that copy the contents of the
+ * temporary variable to and/or from gl_ClipDistance. Each of these
+ * assignments is then lowered to refer to gl_ClipDistanceMESA.
+ *
+ * We need to do a similar replacement for 2D gl_ClipDistance, however since
+ * it's an input, the only case we need to address is where a 1D slice of it
+ * is passed as an "in" parameter to an ir_call, e.g.:
+ *
+ * foo(gl_in[i].gl_ClipDistance)
*/
ir_visitor_status
lower_clip_distance_visitor::visit_leave(ir_call *ir)
formal_param_node = formal_param_node->next;
actual_param_node = actual_param_node->next;
- ir_dereference_variable *deref = actual_param->as_dereference_variable();
- if (deref && deref->var == this->old_clip_distance_var) {
- /* User is trying to pass the whole gl_ClipDistance array to a
- * function call. Since we are reshaping gl_ClipDistance from an
- * array of floats to an array of vec4's, this isn't going to work
- * anymore, so use a temporary array instead.
+ if (this->is_clip_distance_vec8(actual_param)) {
+ /* User is trying to pass the whole 1D gl_ClipDistance array (or a 1D
+ * slice of a 2D gl_ClipDistance array) to a function call. Since we
+ * are reshaping gl_ClipDistance from an array of floats to an array
+ * of vec4's, this isn't going to work anymore, so use a temporary
+ * array instead.
*/
ir_variable *temp_clip_distance = new(ctx) ir_variable(
actual_param->type, "temp_clip_distance", ir_var_temporary);
this->base_ir->insert_before(temp_clip_distance);
actual_param->replace_with(
new(ctx) ir_dereference_variable(temp_clip_distance));
- if (formal_param->mode == ir_var_function_in
- || formal_param->mode == ir_var_function_inout) {
+ if (formal_param->data.mode == ir_var_function_in
+ || formal_param->data.mode == ir_var_function_inout) {
/* Copy from gl_ClipDistance to the temporary before the call.
* Since we are going to insert this copy before the current
* instruction, we need to visit it afterwards to make sure it
*/
ir_assignment *new_assignment = new(ctx) ir_assignment(
new(ctx) ir_dereference_variable(temp_clip_distance),
- new(ctx) ir_dereference_variable(old_clip_distance_var));
+ actual_param->clone(ctx, NULL));
this->base_ir->insert_before(new_assignment);
this->visit_new_assignment(new_assignment);
}
- if (formal_param->mode == ir_var_function_out
- || formal_param->mode == ir_var_function_inout) {
+ if (formal_param->data.mode == ir_var_function_out
+ || formal_param->data.mode == ir_var_function_inout) {
/* Copy from the temporary to gl_ClipDistance after the call.
* Since visit_list_elements() has already decided which
* instruction it's going to visit next, we need to visit
* afterwards to make sure it gets lowered.
*/
ir_assignment *new_assignment = new(ctx) ir_assignment(
- new(ctx) ir_dereference_variable(old_clip_distance_var),
+ actual_param->clone(ctx, NULL),
new(ctx) ir_dereference_variable(temp_clip_distance));
this->base_ir->insert_after(new_assignment);
this->visit_new_assignment(new_assignment);
bool
lower_clip_distance(gl_shader *shader)
{
- lower_clip_distance_visitor v;
+ lower_clip_distance_visitor v(shader->Stage);
visit_list_elements(&v, shader->ir);
- if (v.new_clip_distance_var)
- shader->symbols->add_variable(v.new_clip_distance_var);
+ if (v.new_clip_distance_out_var)
+ shader->symbols->add_variable(v.new_clip_distance_out_var);
+ if (v.new_clip_distance_in_var)
+ shader->symbols->add_variable(v.new_clip_distance_in_var);
return v.progress;
}