glsl: Change built-in constant expression evaluation to run the IR.

This removes code duplication with
ir_expression::constant_expression_value and builtins/ir/*.

Signed-off-by: Olivier Galibert <galibert@pobox.com>
Reviewed-by: Kenneth Graunke <kenneth@whitecape.org>
This commit is contained in:
Olivier Galibert
2012-05-02 23:11:42 +02:00
committed by Kenneth Graunke
parent 2ff7b121ca
commit 363c14ae0c
2 changed files with 156 additions and 380 deletions

View File

@@ -1128,6 +1128,115 @@ ir_call::constant_expression_value(struct hash_table *variable_context)
}
bool ir_function_signature::constant_expression_evaluate_expression_list(const struct exec_list &body,
struct hash_table *variable_context,
ir_constant **result)
{
foreach_list(n, &body) {
ir_instruction *inst = (ir_instruction *)n;
switch(inst->ir_type) {
/* (declare () type symbol) */
case ir_type_variable: {
ir_variable *var = inst->as_variable();
hash_table_insert(variable_context, ir_constant::zero(this, var->type), var);
break;
}
/* (assign [condition] (write-mask) (ref) (value)) */
case ir_type_assignment: {
ir_assignment *asg = inst->as_assignment();
if (asg->condition) {
ir_constant *cond = asg->condition->constant_expression_value(variable_context);
if (!cond)
return false;
if (!cond->get_bool_component(0))
break;
}
ir_constant *store = NULL;
int offset = 0;
asg->lhs->constant_referenced(variable_context, store, offset);
if (!store)
return false;
ir_constant *value = asg->rhs->constant_expression_value(variable_context);
if (!value)
return false;
store->copy_masked_offset(value, offset, asg->write_mask);
break;
}
/* (return (expression)) */
case ir_type_return:
assert (result);
*result = inst->as_return()->value->constant_expression_value(variable_context);
return *result != NULL;
/* (call name (ref) (params))*/
case ir_type_call: {
ir_call *call = inst->as_call();
/* Just say no to void functions in constant expressions. We
* don't need them at that point.
*/
if (!call->return_deref)
return false;
ir_constant *store = NULL;
int offset = 0;
call->return_deref->constant_referenced(variable_context, store, offset);
if (!store)
return false;
ir_constant *value = call->constant_expression_value(variable_context);
if(!value)
return false;
store->copy_offset(value, offset);
break;
}
/* (if condition (then-instructions) (else-instructions)) */
case ir_type_if: {
ir_if *iif = inst->as_if();
ir_constant *cond = iif->condition->constant_expression_value(variable_context);
if (!cond || !cond->type->is_boolean())
return false;
exec_list &branch = cond->get_bool_component(0) ? iif->then_instructions : iif->else_instructions;
*result = NULL;
if (!constant_expression_evaluate_expression_list(branch, variable_context, result))
return false;
/* If there was a return in the branch chosen, drop out now. */
if (*result)
return true;
break;
}
/* Every other expression type, we drop out. */
default:
return false;
}
}
/* Reaching the end of the block is not an error condition */
if (result)
*result = NULL;
return true;
}
ir_constant *
ir_function_signature::constant_expression_value(exec_list *actual_parameters, struct hash_table *variable_context)
{
@@ -1142,396 +1251,48 @@ ir_function_signature::constant_expression_value(exec_list *actual_parameters, s
if (!this->is_builtin)
return NULL;
unsigned num_parameters = 0;
/*
* Of the builtin functions, only the texture lookups and the noise
* ones must not be used in constant expressions. They all include
* specific opcodes so they don't need to be special-cased at this
* point.
*/
/* Initialize the table of dereferencable names with the function
* parameters. Verify their const-ness on the way.
*
* We expect the correctness of the number of parameters to have
* been checked earlier.
*/
hash_table *deref_hash = hash_table_ctor(8, hash_table_pointer_hash,
hash_table_pointer_compare);
/* If "origin" is non-NULL, then the function body is there. So we
* have to use the variable objects from the object with the body,
* but the parameter instanciation on the current object.
*/
const exec_node *parameter_info = origin ? origin->parameters.head : parameters.head;
/* Check if all parameters are constant */
ir_constant *op[3];
foreach_list(n, actual_parameters) {
ir_constant *constant = ((ir_rvalue *) n)->constant_expression_value(variable_context);
if (constant == NULL)
return NULL;
op[num_parameters] = constant;
ir_variable *var = (ir_variable *)parameter_info;
hash_table_insert(deref_hash, constant, var);
assert(num_parameters < 3);
num_parameters++;
parameter_info = parameter_info->next;
}
/* Individual cases below can either:
* - Assign "expr" a new ir_expression to evaluate (for basic opcodes)
* - Fill "data" with appopriate constant data
* - Return an ir_constant directly.
ir_constant *result = NULL;
/* Now run the builtin function until something non-constant
* happens or we get the result.
*/
void *mem_ctx = ralloc_parent(this);
ir_expression *expr = NULL;
if (constant_expression_evaluate_expression_list(origin ? origin->body : body, deref_hash, &result) && result)
result = result->clone(ralloc_parent(this), NULL);
ir_constant_data data;
memset(&data, 0, sizeof(data));
hash_table_dtor(deref_hash);
const char *callee = this->function_name();
if (strcmp(callee, "abs") == 0) {
expr = new(mem_ctx) ir_expression(ir_unop_abs, type, op[0], NULL);
} else if (strcmp(callee, "all") == 0) {
assert(op[0]->type->is_boolean());
for (unsigned c = 0; c < op[0]->type->components(); c++) {
if (!op[0]->value.b[c])
return new(mem_ctx) ir_constant(false);
}
return new(mem_ctx) ir_constant(true);
} else if (strcmp(callee, "any") == 0) {
assert(op[0]->type->is_boolean());
for (unsigned c = 0; c < op[0]->type->components(); c++) {
if (op[0]->value.b[c])
return new(mem_ctx) ir_constant(true);
}
return new(mem_ctx) ir_constant(false);
} else if (strcmp(callee, "acos") == 0) {
assert(op[0]->type->is_float());
for (unsigned c = 0; c < op[0]->type->components(); c++)
data.f[c] = acosf(op[0]->value.f[c]);
} else if (strcmp(callee, "acosh") == 0) {
assert(op[0]->type->is_float());
for (unsigned c = 0; c < op[0]->type->components(); c++)
data.f[c] = acoshf(op[0]->value.f[c]);
} else if (strcmp(callee, "asin") == 0) {
assert(op[0]->type->is_float());
for (unsigned c = 0; c < op[0]->type->components(); c++)
data.f[c] = asinf(op[0]->value.f[c]);
} else if (strcmp(callee, "asinh") == 0) {
assert(op[0]->type->is_float());
for (unsigned c = 0; c < op[0]->type->components(); c++)
data.f[c] = asinhf(op[0]->value.f[c]);
} else if (strcmp(callee, "atan") == 0) {
assert(op[0]->type->is_float());
if (num_parameters == 2) {
assert(op[1]->type->is_float());
for (unsigned c = 0; c < op[0]->type->components(); c++)
data.f[c] = atan2f(op[0]->value.f[c], op[1]->value.f[c]);
} else {
for (unsigned c = 0; c < op[0]->type->components(); c++)
data.f[c] = atanf(op[0]->value.f[c]);
}
} else if (strcmp(callee, "atanh") == 0) {
assert(op[0]->type->is_float());
for (unsigned c = 0; c < op[0]->type->components(); c++)
data.f[c] = atanhf(op[0]->value.f[c]);
} else if (strcmp(callee, "dFdx") == 0 || strcmp(callee, "dFdy") == 0) {
return ir_constant::zero(mem_ctx, type);
} else if (strcmp(callee, "ceil") == 0) {
expr = new(mem_ctx) ir_expression(ir_unop_ceil, type, op[0], NULL);
} else if (strcmp(callee, "clamp") == 0) {
assert(num_parameters == 3);
unsigned c1_inc = op[1]->type->is_scalar() ? 0 : 1;
unsigned c2_inc = op[2]->type->is_scalar() ? 0 : 1;
for (unsigned c = 0, c1 = 0, c2 = 0;
c < op[0]->type->components();
c1 += c1_inc, c2 += c2_inc, c++) {
switch (op[0]->type->base_type) {
case GLSL_TYPE_UINT:
data.u[c] = CLAMP(op[0]->value.u[c], op[1]->value.u[c1],
op[2]->value.u[c2]);
break;
case GLSL_TYPE_INT:
data.i[c] = CLAMP(op[0]->value.i[c], op[1]->value.i[c1],
op[2]->value.i[c2]);
break;
case GLSL_TYPE_FLOAT:
data.f[c] = CLAMP(op[0]->value.f[c], op[1]->value.f[c1],
op[2]->value.f[c2]);
break;
default:
assert(!"Should not get here.");
}
}
} else if (strcmp(callee, "cos") == 0) {
expr = new(mem_ctx) ir_expression(ir_unop_cos, type, op[0], NULL);
} else if (strcmp(callee, "cosh") == 0) {
assert(op[0]->type->is_float());
for (unsigned c = 0; c < op[0]->type->components(); c++)
data.f[c] = coshf(op[0]->value.f[c]);
} else if (strcmp(callee, "cross") == 0) {
assert(op[0]->type == glsl_type::vec3_type);
assert(op[1]->type == glsl_type::vec3_type);
data.f[0] = (op[0]->value.f[1] * op[1]->value.f[2] -
op[1]->value.f[1] * op[0]->value.f[2]);
data.f[1] = (op[0]->value.f[2] * op[1]->value.f[0] -
op[1]->value.f[2] * op[0]->value.f[0]);
data.f[2] = (op[0]->value.f[0] * op[1]->value.f[1] -
op[1]->value.f[0] * op[0]->value.f[1]);
} else if (strcmp(callee, "degrees") == 0) {
assert(op[0]->type->is_float());
for (unsigned c = 0; c < op[0]->type->components(); c++)
data.f[c] = 180.0F / M_PI * op[0]->value.f[c];
} else if (strcmp(callee, "distance") == 0) {
assert(op[0]->type->is_float() && op[1]->type->is_float());
float length_squared = 0.0;
for (unsigned c = 0; c < op[0]->type->components(); c++) {
float t = op[0]->value.f[c] - op[1]->value.f[c];
length_squared += t * t;
}
return new(mem_ctx) ir_constant(sqrtf(length_squared));
} else if (strcmp(callee, "dot") == 0) {
return new(mem_ctx) ir_constant(dot(op[0], op[1]));
} else if (strcmp(callee, "equal") == 0) {
assert(op[0]->type->is_vector() && op[1] && op[1]->type->is_vector());
for (unsigned c = 0; c < op[0]->type->components(); c++) {
switch (op[0]->type->base_type) {
case GLSL_TYPE_UINT:
data.b[c] = op[0]->value.u[c] == op[1]->value.u[c];
break;
case GLSL_TYPE_INT:
data.b[c] = op[0]->value.i[c] == op[1]->value.i[c];
break;
case GLSL_TYPE_FLOAT:
data.b[c] = op[0]->value.f[c] == op[1]->value.f[c];
break;
case GLSL_TYPE_BOOL:
data.b[c] = op[0]->value.b[c] == op[1]->value.b[c];
break;
default:
assert(!"Should not get here.");
}
}
} else if (strcmp(callee, "exp") == 0) {
expr = new(mem_ctx) ir_expression(ir_unop_exp, type, op[0], NULL);
} else if (strcmp(callee, "exp2") == 0) {
expr = new(mem_ctx) ir_expression(ir_unop_exp2, type, op[0], NULL);
} else if (strcmp(callee, "faceforward") == 0) {
if (dot(op[2], op[1]) < 0)
return op[0];
for (unsigned c = 0; c < op[0]->type->components(); c++)
data.f[c] = -op[0]->value.f[c];
} else if (strcmp(callee, "floor") == 0) {
expr = new(mem_ctx) ir_expression(ir_unop_floor, type, op[0], NULL);
} else if (strcmp(callee, "fract") == 0) {
expr = new(mem_ctx) ir_expression(ir_unop_fract, type, op[0], NULL);
} else if (strcmp(callee, "fwidth") == 0) {
return ir_constant::zero(mem_ctx, type);
} else if (strcmp(callee, "greaterThan") == 0) {
assert(op[0]->type->is_vector() && op[1] && op[1]->type->is_vector());
for (unsigned c = 0; c < op[0]->type->components(); c++) {
switch (op[0]->type->base_type) {
case GLSL_TYPE_UINT:
data.b[c] = op[0]->value.u[c] > op[1]->value.u[c];
break;
case GLSL_TYPE_INT:
data.b[c] = op[0]->value.i[c] > op[1]->value.i[c];
break;
case GLSL_TYPE_FLOAT:
data.b[c] = op[0]->value.f[c] > op[1]->value.f[c];
break;
default:
assert(!"Should not get here.");
}
}
} else if (strcmp(callee, "greaterThanEqual") == 0) {
assert(op[0]->type->is_vector() && op[1] && op[1]->type->is_vector());
for (unsigned c = 0; c < op[0]->type->components(); c++) {
switch (op[0]->type->base_type) {
case GLSL_TYPE_UINT:
data.b[c] = op[0]->value.u[c] >= op[1]->value.u[c];
break;
case GLSL_TYPE_INT:
data.b[c] = op[0]->value.i[c] >= op[1]->value.i[c];
break;
case GLSL_TYPE_FLOAT:
data.b[c] = op[0]->value.f[c] >= op[1]->value.f[c];
break;
default:
assert(!"Should not get here.");
}
}
} else if (strcmp(callee, "inversesqrt") == 0) {
expr = new(mem_ctx) ir_expression(ir_unop_rsq, type, op[0], NULL);
} else if (strcmp(callee, "length") == 0) {
return new(mem_ctx) ir_constant(sqrtf(dot(op[0], op[0])));
} else if (strcmp(callee, "lessThan") == 0) {
assert(op[0]->type->is_vector() && op[1] && op[1]->type->is_vector());
for (unsigned c = 0; c < op[0]->type->components(); c++) {
switch (op[0]->type->base_type) {
case GLSL_TYPE_UINT:
data.b[c] = op[0]->value.u[c] < op[1]->value.u[c];
break;
case GLSL_TYPE_INT:
data.b[c] = op[0]->value.i[c] < op[1]->value.i[c];
break;
case GLSL_TYPE_FLOAT:
data.b[c] = op[0]->value.f[c] < op[1]->value.f[c];
break;
default:
assert(!"Should not get here.");
}
}
} else if (strcmp(callee, "lessThanEqual") == 0) {
assert(op[0]->type->is_vector() && op[1] && op[1]->type->is_vector());
for (unsigned c = 0; c < op[0]->type->components(); c++) {
switch (op[0]->type->base_type) {
case GLSL_TYPE_UINT:
data.b[c] = op[0]->value.u[c] <= op[1]->value.u[c];
break;
case GLSL_TYPE_INT:
data.b[c] = op[0]->value.i[c] <= op[1]->value.i[c];
break;
case GLSL_TYPE_FLOAT:
data.b[c] = op[0]->value.f[c] <= op[1]->value.f[c];
break;
default:
assert(!"Should not get here.");
}
}
} else if (strcmp(callee, "log") == 0) {
expr = new(mem_ctx) ir_expression(ir_unop_log, type, op[0], NULL);
} else if (strcmp(callee, "log2") == 0) {
expr = new(mem_ctx) ir_expression(ir_unop_log2, type, op[0], NULL);
} else if (strcmp(callee, "matrixCompMult") == 0) {
assert(op[0]->type->is_float() && op[1]->type->is_float());
for (unsigned c = 0; c < op[0]->type->components(); c++)
data.f[c] = op[0]->value.f[c] * op[1]->value.f[c];
} else if (strcmp(callee, "max") == 0) {
expr = new(mem_ctx) ir_expression(ir_binop_max, type, op[0], op[1]);
} else if (strcmp(callee, "min") == 0) {
expr = new(mem_ctx) ir_expression(ir_binop_min, type, op[0], op[1]);
} else if (strcmp(callee, "mix") == 0) {
assert(op[0]->type->is_float() && op[1]->type->is_float());
if (op[2]->type->is_float()) {
unsigned c2_inc = op[2]->type->is_scalar() ? 0 : 1;
unsigned components = op[0]->type->components();
for (unsigned c = 0, c2 = 0; c < components; c2 += c2_inc, c++) {
data.f[c] = op[0]->value.f[c] * (1 - op[2]->value.f[c2]) +
op[1]->value.f[c] * op[2]->value.f[c2];
}
} else {
assert(op[2]->type->is_boolean());
for (unsigned c = 0; c < op[0]->type->components(); c++)
data.f[c] = op[op[2]->value.b[c] ? 1 : 0]->value.f[c];
}
} else if (strcmp(callee, "mod") == 0) {
expr = new(mem_ctx) ir_expression(ir_binop_mod, type, op[0], op[1]);
} else if (strcmp(callee, "normalize") == 0) {
assert(op[0]->type->is_float());
float length = sqrtf(dot(op[0], op[0]));
if (length == 0)
return ir_constant::zero(mem_ctx, type);
for (unsigned c = 0; c < op[0]->type->components(); c++)
data.f[c] = op[0]->value.f[c] / length;
} else if (strcmp(callee, "not") == 0) {
expr = new(mem_ctx) ir_expression(ir_unop_logic_not, type, op[0], NULL);
} else if (strcmp(callee, "notEqual") == 0) {
assert(op[0]->type->is_vector() && op[1] && op[1]->type->is_vector());
for (unsigned c = 0; c < op[0]->type->components(); c++) {
switch (op[0]->type->base_type) {
case GLSL_TYPE_UINT:
data.b[c] = op[0]->value.u[c] != op[1]->value.u[c];
break;
case GLSL_TYPE_INT:
data.b[c] = op[0]->value.i[c] != op[1]->value.i[c];
break;
case GLSL_TYPE_FLOAT:
data.b[c] = op[0]->value.f[c] != op[1]->value.f[c];
break;
case GLSL_TYPE_BOOL:
data.b[c] = op[0]->value.b[c] != op[1]->value.b[c];
break;
default:
assert(!"Should not get here.");
}
}
} else if (strcmp(callee, "outerProduct") == 0) {
assert(op[0]->type->is_vector() && op[1]->type->is_vector());
const unsigned m = op[0]->type->vector_elements;
const unsigned n = op[1]->type->vector_elements;
for (unsigned j = 0; j < n; j++) {
for (unsigned i = 0; i < m; i++) {
data.f[i+m*j] = op[0]->value.f[i] * op[1]->value.f[j];
}
}
} else if (strcmp(callee, "pow") == 0) {
expr = new(mem_ctx) ir_expression(ir_binop_pow, type, op[0], op[1]);
} else if (strcmp(callee, "radians") == 0) {
assert(op[0]->type->is_float());
for (unsigned c = 0; c < op[0]->type->components(); c++)
data.f[c] = M_PI / 180.0F * op[0]->value.f[c];
} else if (strcmp(callee, "reflect") == 0) {
assert(op[0]->type->is_float());
float dot_NI = dot(op[1], op[0]);
for (unsigned c = 0; c < op[0]->type->components(); c++)
data.f[c] = op[0]->value.f[c] - 2 * dot_NI * op[1]->value.f[c];
} else if (strcmp(callee, "refract") == 0) {
const float eta = op[2]->value.f[0];
const float dot_NI = dot(op[1], op[0]);
const float k = 1.0F - eta * eta * (1.0F - dot_NI * dot_NI);
if (k < 0.0) {
return ir_constant::zero(mem_ctx, type);
} else {
for (unsigned c = 0; c < type->components(); c++) {
data.f[c] = eta * op[0]->value.f[c] - (eta * dot_NI + sqrtf(k))
* op[1]->value.f[c];
}
}
} else if (strcmp(callee, "round") == 0 ||
strcmp(callee, "roundEven") == 0) {
expr = new(mem_ctx) ir_expression(ir_unop_round_even, op[0]);
} else if (strcmp(callee, "sign") == 0) {
expr = new(mem_ctx) ir_expression(ir_unop_sign, type, op[0], NULL);
} else if (strcmp(callee, "sin") == 0) {
expr = new(mem_ctx) ir_expression(ir_unop_sin, type, op[0], NULL);
} else if (strcmp(callee, "sinh") == 0) {
assert(op[0]->type->is_float());
for (unsigned c = 0; c < op[0]->type->components(); c++)
data.f[c] = sinhf(op[0]->value.f[c]);
} else if (strcmp(callee, "smoothstep") == 0) {
assert(num_parameters == 3);
assert(op[1]->type == op[0]->type);
unsigned edge_inc = op[0]->type->is_scalar() ? 0 : 1;
for (unsigned c = 0, e = 0; c < type->components(); e += edge_inc, c++) {
const float edge0 = op[0]->value.f[e];
const float edge1 = op[1]->value.f[e];
if (edge0 == edge1) {
data.f[c] = 0.0; /* Avoid a crash - results are undefined anyway */
} else {
const float numerator = op[2]->value.f[c] - edge0;
const float denominator = edge1 - edge0;
const float t = CLAMP(numerator/denominator, 0, 1);
data.f[c] = t * t * (3 - 2 * t);
}
}
} else if (strcmp(callee, "sqrt") == 0) {
expr = new(mem_ctx) ir_expression(ir_unop_sqrt, type, op[0], NULL);
} else if (strcmp(callee, "step") == 0) {
assert(op[0]->type->is_float() && op[1]->type->is_float());
/* op[0] (edge) may be either a scalar or a vector */
const unsigned c0_inc = op[0]->type->is_scalar() ? 0 : 1;
for (unsigned c = 0, c0 = 0; c < type->components(); c0 += c0_inc, c++)
data.f[c] = (op[1]->value.f[c] < op[0]->value.f[c0]) ? 0.0F : 1.0F;
} else if (strcmp(callee, "tan") == 0) {
assert(op[0]->type->is_float());
for (unsigned c = 0; c < op[0]->type->components(); c++)
data.f[c] = tanf(op[0]->value.f[c]);
} else if (strcmp(callee, "tanh") == 0) {
assert(op[0]->type->is_float());
for (unsigned c = 0; c < op[0]->type->components(); c++)
data.f[c] = tanhf(op[0]->value.f[c]);
} else if (strcmp(callee, "transpose") == 0) {
assert(op[0]->type->is_matrix());
const unsigned n = op[0]->type->vector_elements;
const unsigned m = op[0]->type->matrix_columns;
for (unsigned j = 0; j < m; j++) {
for (unsigned i = 0; i < n; i++) {
data.f[m*i+j] += op[0]->value.f[i+n*j];
}
}
} else if (strcmp(callee, "trunc") == 0) {
expr = new(mem_ctx) ir_expression(ir_unop_trunc, op[0]);
} else {
/* Unsupported builtin - some are not allowed in constant expressions. */
return NULL;
}
if (expr != NULL)
return expr->constant_expression_value();
return new(mem_ctx) ir_constant(type, &data);
return result;
}