From d70771752f7df031c4fd9a7ad17ddcef6d91377a Mon Sep 17 00:00:00 2001 From: Brian Date: Thu, 18 Jan 2007 15:35:44 -0700 Subject: [PATCH] Reimplement code for swizzling so that expressions like (p+q).x for vectors p and q works correctly. --- src/mesa/shader/slang/slang_codegen.c | 84 +++++++++++------ src/mesa/shader/slang/slang_emit.c | 127 ++++++++++++++------------ src/mesa/shader/slang/slang_emit.h | 4 - src/mesa/shader/slang/slang_ir.h | 5 +- 4 files changed, 129 insertions(+), 91 deletions(-) diff --git a/src/mesa/shader/slang/slang_codegen.c b/src/mesa/shader/slang/slang_codegen.c index 85f1f0ccfd3..b389f411773 100644 --- a/src/mesa/shader/slang/slang_codegen.c +++ b/src/mesa/shader/slang/slang_codegen.c @@ -57,11 +57,13 @@ _slang_gen_operation(slang_assemble_ctx * A, slang_operation *oper); /** * Lookup a named constant and allocate storage for the parameter in * the given parameter list. + * \param swizzleOut returns swizzle mask for accessing the constant * \return position of the constant in the paramList. */ static GLint slang_lookup_constant(const char *name, GLint index, - struct gl_program_parameter_list *paramList) + struct gl_program_parameter_list *paramList, + GLuint *swizzleOut) { struct constant_info { const char *Name; @@ -82,7 +84,6 @@ slang_lookup_constant(const char *name, GLint index, { NULL, 0 } }; GLuint i; - GLuint swizzle; /* XXX use this */ for (i = 0; info[i].Name; i++) { if (strcmp(info[i].Name, name) == 0) { @@ -92,7 +93,7 @@ slang_lookup_constant(const char *name, GLint index, _mesa_GetFloatv(info[i].Token, &value); ASSERT(value >= 0.0); /* sanity check that glGetFloatv worked */ /* XXX named constant! */ - pos = _mesa_add_unnamed_constant(paramList, &value, 1, &swizzle); + pos = _mesa_add_unnamed_constant(paramList, &value, 1, swizzleOut); return pos; } } @@ -299,8 +300,10 @@ slang_allocate_storage(slang_assemble_ctx *A, slang_ir_node *n) n->Store->Index = i; } else if (n->Store->File == PROGRAM_CONSTANT) { - GLint i = slang_lookup_constant(varName, 0, prog->Parameters); + GLint i = slang_lookup_constant(varName, 0, prog->Parameters, + &n->Store->Swizzle); assert(i >= 0); + assert(n->Store->Size == 1); n->Store->Index = i; } } @@ -518,7 +521,6 @@ new_node(slang_ir_opcode op, slang_ir_node *left, slang_ir_node *right) n->Opcode = op; n->Children[0] = left; n->Children[1] = right; - n->Swizzle = SWIZZLE_NOOP; n->Writemask = WRITEMASK_XYZW; } return n; @@ -544,11 +546,14 @@ new_label(slang_atom labName) static slang_ir_node * new_float_literal(float x, float y, float z, float w) { + GLuint size = 4; /* XXX fix */ slang_ir_node *n = new_node(IR_FLOAT, NULL, NULL); n->Value[0] = x; n->Value[1] = y; n->Value[2] = z; n->Value[3] = w; + /* allocate a storage object, but compute actual location (Index) later */ + n->Store = _slang_new_ir_storage(PROGRAM_CONSTANT, -1, size); return n; } @@ -583,7 +588,6 @@ new_jump(slang_atom target) static slang_ir_node * new_var(slang_assemble_ctx *A, slang_operation *oper, slang_atom name) { - GLuint swizzle = SWIZZLE_NOOP; slang_variable *v = _slang_locate_variable(oper->locals, name, GL_TRUE); slang_ir_node *n = new_node(IR_VAR, NULL, NULL); if (!v) { @@ -592,10 +596,7 @@ new_var(slang_assemble_ctx *A, slang_operation *oper, slang_atom name) } assert(!oper->var || oper->var == v); v->used = GL_TRUE; - - n->Swizzle = swizzle; n->Var = v; - slang_allocate_storage(A, n); return n; @@ -667,7 +668,9 @@ slang_inline_asm_function(slang_assemble_ctx *A, slang_operation *inlined = slang_operation_new(1); /*assert(oper->type == slang_oper_call); or vec4_add, etc */ + /* printf("Inline asm %s\n", (char*) fun->header.a_name); + */ inlined->type = fun->body->children[0].type; inlined->a_id = fun->body->children[0].a_id; inlined->num_children = numArgs; @@ -871,9 +874,11 @@ slang_inline_function_call(slang_assemble_ctx * A, slang_function *fun, substNew = (slang_operation **) _mesa_calloc(totalArgs * sizeof(slang_operation *)); +#if 0 printf("Inline call to %s (total vars=%d nparams=%d)\n", (char *) fun->header.a_name, fun->parameters->num_variables, numArgs); +#endif if (haveRetValue && !returnOper) { /* Create 3-child comma sequence for inlined code: @@ -1022,7 +1027,9 @@ slang_inline_function_call(slang_assemble_ctx * A, slang_function *fun, slang_operation *decl = slang_operation_insert(&inlined->num_children, &inlined->children, numCopyIn); + /* printf("COPY_IN %s from expr\n", (char*)p->a_name); + */ decl->type = slang_oper_variable_decl; assert(decl->locals); decl->locals = fun->parameters; @@ -1073,13 +1080,12 @@ slang_inline_function_call(slang_assemble_ctx * A, slang_function *fun, _mesa_free(substOld); _mesa_free(substNew); +#if 0 printf("Done Inline call to %s (total vars=%d nparams=%d)\n", (char *) fun->header.a_name, fun->parameters->num_variables, numArgs); - - /* slang_print_tree(top, 0); - */ +#endif return top; } @@ -1123,7 +1129,6 @@ _slang_gen_function_call(slang_assemble_ctx *A, slang_function *fun, assert(inlined->locals); printf("*** Inlined code for call to %s:\n", (char*) fun->header.a_name); - slang_print_tree(oper, 10); printf("\n"); #endif @@ -1204,11 +1209,15 @@ _slang_gen_asm(slang_assemble_ctx *A, slang_operation *oper, assert(info->NumParams <= 2); if (info->NumParams == oper->num_children) { - /* storage for result not specified */ + /* Storage for result is not specified. + * Children[0], [1] are the operands. + */ firstOperand = 0; } else { - /* storage for result (child[0]) is specified */ + /* Storage for result (child[0]) is specified. + * Children[1], [2] are the operands. + */ firstOperand = 1; } @@ -1229,9 +1238,9 @@ _slang_gen_asm(slang_assemble_ctx *A, slang_operation *oper, slang_ir_node *n0; dest_oper = &oper->children[0]; - if (dest_oper->type == slang_oper_field) { + while /*if*/ (dest_oper->type == slang_oper_field) { /* writemask */ - writemask = make_writemask((char*) dest_oper->a_id); + writemask &= /*=*/make_writemask((char*) dest_oper->a_id); dest_oper = &dest_oper->children[0]; } @@ -1829,6 +1838,20 @@ _slang_gen_assignment(slang_assemble_ctx * A, slang_operation *oper) } +static slang_ir_node * +_slang_gen_swizzle(slang_ir_node *child, GLuint swizzle) +{ + slang_ir_node *n = new_node(IR_SWIZZLE, child, NULL); + if (n) { + n->Store = _slang_new_ir_storage(child->Store->File, + child->Store->Index, + child->Store->Size); + n->Store->Swizzle = swizzle; + } + return n; +} + + /** * Generate IR tree for referencing a field in a struct (or basic vector type) */ @@ -1845,28 +1868,35 @@ _slang_gen_field(slang_assemble_ctx * A, slang_operation *oper) const GLuint rows = _slang_type_dim(ti.spec.type); slang_swizzle swz; slang_ir_node *n; + GLuint swizzle; if (!_slang_is_swizzle((char *) oper->a_id, rows, &swz)) { RETURN_ERROR("Bad swizzle", 0); } + swizzle = MAKE_SWIZZLE4(swz.swizzle[0], + swz.swizzle[1], + swz.swizzle[2], + swz.swizzle[3]); + n = _slang_gen_operation(A, &oper->children[0]); - n->Swizzle = MAKE_SWIZZLE4(swz.swizzle[0], - swz.swizzle[1], - swz.swizzle[2], - swz.swizzle[3]); + /* create new parent node with swizzle */ + n = _slang_gen_swizzle(n, swizzle); return n; } else if (ti.spec.type == slang_spec_float) { const GLuint rows = 1; slang_swizzle swz; slang_ir_node *n; + GLuint swizzle; if (!_slang_is_swizzle((char *) oper->a_id, rows, &swz)) { RETURN_ERROR("Bad swizzle", 0); } + swizzle = MAKE_SWIZZLE4(swz.swizzle[0], + swz.swizzle[1], + swz.swizzle[2], + swz.swizzle[3]); n = _slang_gen_operation(A, &oper->children[0]); - n->Swizzle = MAKE_SWIZZLE4(swz.swizzle[0], - swz.swizzle[1], - swz.swizzle[2], - swz.swizzle[3]); + /* create new parent node with swizzle */ + n = _slang_gen_swizzle(n, swizzle); return n; } else { @@ -1904,7 +1934,8 @@ _slang_gen_subscript(slang_assemble_ctx * A, slang_operation *oper) n = _slang_gen_operation(A, &oper->children[0]); if (n) { /* use swizzle to access the element */ - n->Swizzle = SWIZZLE_X + index; + n = _slang_gen_swizzle(n, SWIZZLE_X + index); + /*n->Store = _slang_clone_ir_storage_swz(n->Store, */ n->Writemask = WRITEMASK_X << index; } return n; @@ -2372,7 +2403,6 @@ _slang_codegen_global_variable(slang_assemble_ctx *A, slang_variable *var, /* Generate IR_MOVE instruction to initialize the variable */ lhs = new_node(IR_VAR, NULL, NULL); lhs->Var = var; - lhs->Swizzle = SWIZZLE_NOOP; lhs->Store = n->Store; /* constant folding, etc */ diff --git a/src/mesa/shader/slang/slang_emit.c b/src/mesa/shader/slang/slang_emit.c index 8e628173368..a30552b909c 100644 --- a/src/mesa/shader/slang/slang_emit.c +++ b/src/mesa/shader/slang/slang_emit.c @@ -101,6 +101,7 @@ static slang_ir_info IrInfo[] = { { IR_FLOAT, "IR_FLOAT", 0, 0, 0 }, { IR_FIELD, "IR_FIELD", 0, 0, 0 }, { IR_ELEMENT, "IR_ELEMENT", 0, 0, 0 }, + { IR_SWIZZLE, "IR_SWIZZLE", 0, 0, 0 }, { IR_NOP, NULL, OPCODE_NOP, 0, 0 } }; @@ -124,6 +125,24 @@ slang_ir_name(slang_ir_opcode opcode) } +#if 0 +/** + * Swizzle a swizzle. + */ +static GLuint +swizzle_compose(GLuint swz1, GLuint swz2) +{ + GLuint i, swz, s[4]; + for (i = 0; i < 4; i++) { + GLuint c = GET_SWZ(swz1, i); + s[i] = GET_SWZ(swz2, c); + } + swz = MAKE_SWIZZLE4(s[0], s[1], s[2], s[3]); + return swz; +} +#endif + + slang_ir_storage * _slang_new_ir_storage(enum register_file file, GLint index, GLint size) { @@ -133,20 +152,12 @@ _slang_new_ir_storage(enum register_file file, GLint index, GLint size) st->File = file; st->Index = index; st->Size = size; + st->Swizzle = SWIZZLE_NOOP; } return st; } -slang_ir_storage * -_slang_clone_ir_storage(slang_ir_storage *store) -{ - slang_ir_storage *clone - = _slang_new_ir_storage(store->File, store->Index, store->Size); - return clone; -} - - static const char * swizzle_string(GLuint swizzle) { @@ -257,7 +268,7 @@ slang_print_ir(const slang_ir_node *n, int indent) break; case IR_VAR: printf("VAR %s%s at %s store %p\n", - (char *) n->Var->a_name, swizzle_string(n->Swizzle), + (char *) n->Var->a_name, swizzle_string(n->Store->Swizzle), storage_string(n->Store), (void*) n->Store); break; case IR_VAR_DECL: @@ -270,7 +281,7 @@ slang_print_ir(const slang_ir_node *n, int indent) slang_print_ir(n->Children[0], indent+3); break; case IR_CALL: - printf("ASMCALL %s(%d args)\n", n->Target, n->Swizzle); + printf("ASMCALL %s(%d args)\n", n->Target, 0/*XXX*/); break; case IR_FLOAT: printf("FLOAT %f %f %f %f\n", @@ -279,9 +290,14 @@ slang_print_ir(const slang_ir_node *n, int indent) case IR_I_TO_F: printf("INT_TO_FLOAT %d\n", (int) n->Value[0]); break; + case IR_SWIZZLE: + printf("SWIZZLE %s of (store %p) \n", + swizzle_string(n->Store->Swizzle), (void*) n->Store); + slang_print_ir(n->Children[0], indent + 3); + break; default: - printf("%s (%p, %p)\n", slang_ir_name(n->Opcode), - (void*) n->Children[0], (void*) n->Children[1]); + printf("%s (%p, %p) (store %p)\n", slang_ir_name(n->Opcode), + (void*) n->Children[0], (void*) n->Children[1], (void*) n->Store); slang_print_ir(n->Children[0], indent+3); slang_print_ir(n->Children[1], indent+3); } @@ -322,37 +338,6 @@ free_temp_storage(slang_var_table *vt, slang_ir_node *n) } -/** - * Allocate storage for a floating point constant. - */ -static slang_ir_storage * -alloc_constant(const GLfloat v[], GLuint size, struct gl_program *prog) -{ - GLuint swizzle; - GLint ind = _mesa_add_unnamed_constant(prog->Parameters, v, size, &swizzle); - slang_ir_storage *st = _slang_new_ir_storage(PROGRAM_CONSTANT, ind, size); - return st; -} - - -/** - * Swizzle a swizzle. - */ -#if 0 -static GLuint -swizzle_compose(GLuint swz1, GLuint swz2) -{ - GLuint i, swz, s[4]; - for (i = 0; i < 4; i++) { - GLuint c = GET_SWZ(swz1, i); - s[i] = GET_SWZ(swz2, c); - } - swz = MAKE_SWIZZLE4(s[0], s[1], s[2], s[3]); - return swz; -} -#endif - - /** * Convert IR storage to an instruction dst register. */ @@ -366,6 +351,7 @@ storage_to_dst_reg(struct prog_dst_register *dst, const slang_ir_storage *st, WRITEMASK_X | WRITEMASK_Y | WRITEMASK_Z, WRITEMASK_X | WRITEMASK_Y | WRITEMASK_Z | WRITEMASK_W }; + assert(st->Index >= 0 && st->Index <= 16); dst->File = st->File; dst->Index = st->Index; assert(st->File != PROGRAM_UNDEFINED); @@ -388,7 +374,7 @@ storage_to_src_reg(struct prog_src_register *src, const slang_ir_storage *st, MAKE_SWIZZLE4(SWIZZLE_X, SWIZZLE_Y, SWIZZLE_Z, SWIZZLE_W), MAKE_SWIZZLE4(SWIZZLE_X, SWIZZLE_Y, SWIZZLE_Z, SWIZZLE_W) }; - + assert(st->File >= 0 && st->File <= 16); src->File = st->File; src->Index = st->Index; assert(st->File != PROGRAM_UNDEFINED); @@ -396,8 +382,8 @@ storage_to_src_reg(struct prog_src_register *src, const slang_ir_storage *st, assert(st->Size <= 4); /* XXX swizzling logic here may need some work */ /*src->Swizzle = swizzle_compose(swizzle, defaultSwizzle[st->Size - 1]);*/ - if (swizzle != SWIZZLE_NOOP) - src->Swizzle = swizzle; + if (st->Swizzle != SWIZZLE_NOOP) + src->Swizzle = st->Swizzle; else src->Swizzle = defaultSwizzle[st->Size - 1]; } @@ -448,9 +434,9 @@ emit_binop(slang_var_table *vt, slang_ir_node *n, struct gl_program *prog) /* gen this instruction */ inst = new_instruction(prog, info->InstOpcode); storage_to_src_reg(&inst->SrcReg[0], n->Children[0]->Store, - n->Children[0]->Swizzle); + /**n->Children[0]->Swizzle*/0); storage_to_src_reg(&inst->SrcReg[1], n->Children[1]->Store, - n->Children[1]->Swizzle); + /**n->Children[1]->Swizzle*/0); free_temp_storage(vt, n->Children[0]); free_temp_storage(vt, n->Children[1]); @@ -480,7 +466,7 @@ emit_unop(slang_var_table *vt, slang_ir_node *n, struct gl_program *prog) /* gen this instruction */ inst = new_instruction(prog, info->InstOpcode); storage_to_src_reg(&inst->SrcReg[0], n->Children[0]->Store, - n->Children[0]->Swizzle); + /**n->Children[0]->Swizzle*/0); free_temp_storage(vt, n->Children[0]); if (!n->Store) { @@ -511,7 +497,7 @@ emit_negation(slang_var_table *vt, slang_ir_node *n, struct gl_program *prog) inst = new_instruction(prog, OPCODE_MOV); storage_to_dst_reg(&inst->DstReg, n->Store, n->Writemask); storage_to_src_reg(&inst->SrcReg[0], n->Children[0]->Store, - n->Children[0]->Swizzle); + /**n->Children[0]->Swizzle*/0); inst->SrcReg[0].NegateBase = NEGATE_XYZW; inst->Comment = n->Comment; return inst; @@ -574,7 +560,7 @@ emit_tex(slang_var_table *vt, slang_ir_node *n, struct gl_program *prog) /* Child[1] is the coord */ storage_to_src_reg(&inst->SrcReg[0], n->Children[1]->Store, - n->Children[1]->Swizzle); + /**n->Children[1]->Swizzle*/0); /* Child[0] is the sampler (a uniform which'll indicate the texture unit) */ assert(n->Children[0]->Store); @@ -598,6 +584,8 @@ emit_move(slang_var_table *vt, slang_ir_node *n, struct gl_program *prog) assert(n->Children[1]); inst = emit(vt, n->Children[1], prog); + assert(n->Children[1]->Store->Index >= 0); + /* lhs */ emit(vt, n->Children[0], prog); @@ -611,6 +599,8 @@ emit_move(slang_var_table *vt, slang_ir_node *n, struct gl_program *prog) n->Children[1]->Store->Size); *n->Children[1]->Store = *n->Children[0]->Store; /* fixup the prev (RHS) instruction */ + assert(n->Children[0]->Store->Index >= 0); + assert(n->Children[0]->Store->Index < 16); storage_to_dst_reg(&inst->DstReg, n->Children[0]->Store, n->Writemask); return inst; } @@ -623,7 +613,7 @@ emit_move(slang_var_table *vt, slang_ir_node *n, struct gl_program *prog) slang_ir_storage srcStore = *n->Children[1]->Store; GLint size = srcStore.Size; ASSERT(n->Children[0]->Writemask == WRITEMASK_XYZW); - ASSERT(n->Children[1]->Swizzle == SWIZZLE_NOOP); + ASSERT(n->Children[1]->Store->Swizzle == SWIZZLE_NOOP); dstStore.Size = 4; srcStore.Size = 4; while (size >= 4) { @@ -631,7 +621,7 @@ emit_move(slang_var_table *vt, slang_ir_node *n, struct gl_program *prog) inst->Comment = _mesa_strdup("IR_MOVE block"); storage_to_dst_reg(&inst->DstReg, &dstStore, n->Writemask); storage_to_src_reg(&inst->SrcReg[0], &srcStore, - n->Children[1]->Swizzle); + /**n->Children[1]->Swizzle*/0); srcStore.Index++; dstStore.Index++; size -= 4; @@ -639,9 +629,11 @@ emit_move(slang_var_table *vt, slang_ir_node *n, struct gl_program *prog) } else { inst = new_instruction(prog, OPCODE_MOV); + assert(n->Children[0]->Store->Index >= 0); + assert(n->Children[0]->Store->Index < 16); storage_to_dst_reg(&inst->DstReg, n->Children[0]->Store, n->Writemask); storage_to_src_reg(&inst->SrcReg[0], n->Children[1]->Store, - n->Children[1]->Swizzle); + /**n->Children[1]->Swizzle*/0); } /* XXX is this test correct? */ if (_slang_is_temp(vt, n->Children[1]->Store->Index)) { @@ -680,7 +672,7 @@ emit_cond(slang_var_table *vt, slang_ir_node *n, struct gl_program *prog) inst->CondUpdate = GL_TRUE; storage_to_dst_reg(&inst->DstReg, n->Store, n->Writemask); storage_to_src_reg(&inst->SrcReg[0], n->Children[0]->Store, - n->Children[0]->Swizzle); + /**n->Children[0]->Swizzle*/0); _slang_free_temp(vt, n->Store->Index, n->Store->Size); return inst; /* XXX or null? */ } @@ -726,6 +718,11 @@ emit(slang_var_table *vt, slang_ir_node *n, struct gl_program *prog) /* a regular variable */ _slang_add_variable(vt, n->Var); n->Store->Index = _slang_alloc_var(vt, n->Store->Size); + /* + printf("IR_VAR_DECL %s %d store %p\n", + (char*) n->Var->a_name, n->Store->Index, (void*) n->Store); + */ + assert(n->Var->aux == n->Store); } assert(n->Store->Index >= 0); break; @@ -762,6 +759,17 @@ emit(slang_var_table *vt, slang_ir_node *n, struct gl_program *prog) } return NULL; /* no instruction */ + case IR_SWIZZLE: + /* swizzled storage access */ + (void) emit(vt, n->Children[0], prog); + /* "pull-up" the child's storage info, applying our swizzle info */ + n->Store->File = n->Children[0]->Store->File; + n->Store->Index = n->Children[0]->Store->Index; + n->Store->Size = n->Children[0]->Store->Size; + assert(n->Store->Index >= 0); + /* XXX compose swizzles here!!! */ + return NULL; + /* Simple binary operators */ case IR_ADD: case IR_SUB: @@ -798,8 +806,11 @@ emit(slang_var_table *vt, slang_ir_node *n, struct gl_program *prog) case IR_NEG: return emit_negation(vt, n, prog); case IR_FLOAT: - n->Store = alloc_constant(n->Value, 4, prog); /*XXX fix size */ - break; + /* find storage location for this float */ + n->Store->Index = _mesa_add_unnamed_constant(prog->Parameters, n->Value, + 4, &n->Store->Swizzle); + assert(n->Store->Index >= 0); + return NULL; case IR_MOVE: return emit_move(vt, n, prog); diff --git a/src/mesa/shader/slang/slang_emit.h b/src/mesa/shader/slang/slang_emit.h index 7a845feac2b..1b792402dac 100644 --- a/src/mesa/shader/slang/slang_emit.h +++ b/src/mesa/shader/slang/slang_emit.h @@ -40,10 +40,6 @@ extern slang_ir_storage * _slang_new_ir_storage(enum register_file file, GLint index, GLint size); -extern slang_ir_storage * -_slang_clone_ir_storage(slang_ir_storage *store); - - extern GLboolean _slang_emit_code(slang_ir_node *n, slang_var_table *vartable, struct gl_program *prog, GLboolean withEnd); diff --git a/src/mesa/shader/slang/slang_ir.h b/src/mesa/shader/slang/slang_ir.h index 7fe3f7f153b..34dd1bb6910 100644 --- a/src/mesa/shader/slang/slang_ir.h +++ b/src/mesa/shader/slang/slang_ir.h @@ -83,6 +83,7 @@ typedef enum IR_VAR, /* variable reference */ IR_VAR_DECL,/* var declaration */ IR_ELEMENT, /* array element */ + IR_SWIZZLE, /* swizzled storage access */ IR_TEX, /* texture lookup */ IR_TEXB, /* texture lookup with LOD bias */ IR_TEXP, /* texture lookup with projection */ @@ -101,6 +102,7 @@ typedef struct enum register_file File; /**< PROGRAM_TEMPORARY, PROGRAM_INPUT, etc */ GLint Index; /**< -1 means unallocated */ GLint Size; /**< number of floats */ + GLuint Swizzle; } slang_ir_storage; @@ -113,11 +115,10 @@ typedef struct slang_ir_node_ struct slang_ir_node_ *Children[2]; const char *Comment; const char *Target; - GLuint Swizzle; GLuint Writemask; /**< If Opcode == IR_MOVE */ GLfloat Value[4]; /**< If Opcode == IR_FLOAT */ slang_variable *Var; - slang_ir_storage *Store; + slang_ir_storage *Store; /**< location of result of this operation */ } slang_ir_node; -- 2.30.2