/* * Copyright (C) 2018 Jonathan Marek * * Permission is hereby granted, free of charge, to any person obtaining a * copy of this software and associated documentation files (the "Software"), * to deal in the Software without restriction, including without limitation * the rights to use, copy, modify, merge, publish, distribute, sublicense, * and/or sell copies of the Software, and to permit persons to whom the * Software is furnished to do so, subject to the following conditions: * * The above copyright notice and this permission notice (including the next * paragraph) shall be included in all copies or substantial portions of the * Software. * * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE * SOFTWARE. * * Authors: * Jonathan Marek */ #include "ir2_private.h" #include "freedreno_util.h" #include "fd2_program.h" static const nir_shader_compiler_options options = { .lower_fpow = true, .lower_flrp32 = true, .lower_fmod = true, .lower_fdiv = true, .lower_fceil = true, .fuse_ffma = true, /* .fdot_replicates = true, it is replicated, but it makes things worse */ .lower_all_io_to_temps = true, .vertex_id_zero_based = true, /* its not implemented anyway */ .lower_bitops = true, .lower_rotate = true, .lower_vector_cmp = true, .lower_fdph = true, }; const nir_shader_compiler_options * ir2_get_compiler_options(void) { return &options; } #define OPT(nir, pass, ...) ({ \ bool this_progress = false; \ NIR_PASS(this_progress, nir, pass, ##__VA_ARGS__); \ this_progress; \ }) #define OPT_V(nir, pass, ...) NIR_PASS_V(nir, pass, ##__VA_ARGS__) static void ir2_optimize_loop(nir_shader *s) { bool progress; do { progress = false; OPT_V(s, nir_lower_vars_to_ssa); progress |= OPT(s, nir_opt_copy_prop_vars); progress |= OPT(s, nir_copy_prop); progress |= OPT(s, nir_opt_dce); progress |= OPT(s, nir_opt_cse); /* progress |= OPT(s, nir_opt_gcm, true); */ progress |= OPT(s, nir_opt_peephole_select, UINT_MAX, true, true); progress |= OPT(s, nir_opt_intrinsics); progress |= OPT(s, nir_opt_algebraic); progress |= OPT(s, nir_opt_constant_folding); progress |= OPT(s, nir_opt_dead_cf); if (OPT(s, nir_opt_trivial_continues)) { progress |= true; /* If nir_opt_trivial_continues makes progress, then we need to clean * things up if we want any hope of nir_opt_if or nir_opt_loop_unroll * to make progress. */ OPT(s, nir_copy_prop); OPT(s, nir_opt_dce); } progress |= OPT(s, nir_opt_loop_unroll, nir_var_all); progress |= OPT(s, nir_opt_if, false); progress |= OPT(s, nir_opt_remove_phis); progress |= OPT(s, nir_opt_undef); } while (progress); } /* trig workarounds is the same as ir3.. but we don't want to include ir3 */ bool ir3_nir_apply_trig_workarounds(nir_shader * shader); int ir2_optimize_nir(nir_shader *s, bool lower) { struct nir_lower_tex_options tex_options = { .lower_txp = ~0u, .lower_rect = 0, }; if (fd_mesa_debug & FD_DBG_DISASM) { debug_printf("----------------------\n"); nir_print_shader(s, stdout); debug_printf("----------------------\n"); } OPT_V(s, nir_lower_regs_to_ssa); OPT_V(s, nir_lower_vars_to_ssa); OPT_V(s, nir_lower_indirect_derefs, nir_var_shader_in | nir_var_shader_out); if (lower) { OPT_V(s, ir3_nir_apply_trig_workarounds); OPT_V(s, nir_lower_tex, &tex_options); } ir2_optimize_loop(s); OPT_V(s, nir_remove_dead_variables, nir_var_function_temp); OPT_V(s, nir_opt_sink, nir_move_const_undef); /* TODO we dont want to get shaders writing to depth for depth textures */ if (s->info.stage == MESA_SHADER_FRAGMENT) { nir_foreach_variable(var, &s->outputs) { if (var->data.location == FRAG_RESULT_DEPTH) return -1; } } return 0; } static struct ir2_src load_const(struct ir2_context *ctx, float *value_f, unsigned ncomp) { struct fd2_shader_stateobj *so = ctx->so; unsigned imm_ncomp, swiz, idx, i, j; uint32_t *value = (uint32_t*) value_f; /* try to merge with existing immediate (TODO: try with neg) */ for (idx = 0; idx < so->num_immediates; idx++) { swiz = 0; imm_ncomp = so->immediates[idx].ncomp; for (i = 0; i < ncomp; i++) { for (j = 0; j < imm_ncomp; j++) { if (value[i] == so->immediates[idx].val[j]) break; } if (j == imm_ncomp) { if (j == 4) break; so->immediates[idx].val[imm_ncomp++] = value[i]; } swiz |= swiz_set(j, i); } /* matched all components */ if (i == ncomp) break; } /* need to allocate new immediate */ if (idx == so->num_immediates) { swiz = 0; imm_ncomp = 0; for (i = 0; i < ncomp; i++) { for (j = 0; j < imm_ncomp; j++) { if (value[i] == ctx->so->immediates[idx].val[j]) break; } if (j == imm_ncomp) { so->immediates[idx].val[imm_ncomp++] = value[i]; } swiz |= swiz_set(j, i); } so->num_immediates++; } so->immediates[idx].ncomp = imm_ncomp; if (ncomp == 1) swiz = swiz_merge(swiz, IR2_SWIZZLE_XXXX); return ir2_src(so->first_immediate + idx, swiz, IR2_SRC_CONST); } struct ir2_src ir2_zero(struct ir2_context *ctx) { return load_const(ctx, (float[]) {0.0f}, 1); } static void update_range(struct ir2_context *ctx, struct ir2_reg *reg) { if (!reg->initialized) { reg->initialized = true; reg->loop_depth = ctx->loop_depth; } if (ctx->loop_depth > reg->loop_depth) { reg->block_idx_free = ctx->loop_last_block[reg->loop_depth + 1]; } else { reg->loop_depth = ctx->loop_depth; reg->block_idx_free = -1; } /* for regs we want to free at the end of the loop in any case * XXX dont do this for ssa */ if (reg->loop_depth) reg->block_idx_free = ctx->loop_last_block[reg->loop_depth]; } static struct ir2_src make_src(struct ir2_context *ctx, nir_src src) { struct ir2_src res = {}; struct ir2_reg *reg; nir_const_value *const_value = nir_src_as_const_value(src); if (const_value) { assert(src.is_ssa); float c[src.ssa->num_components]; nir_const_value_to_array(c, const_value, src.ssa->num_components, f32); return load_const(ctx, c, src.ssa->num_components); } if (!src.is_ssa) { res.num = src.reg.reg->index; res.type = IR2_SRC_REG; reg = &ctx->reg[res.num]; } else { assert(ctx->ssa_map[src.ssa->index] >= 0); res.num = ctx->ssa_map[src.ssa->index]; res.type = IR2_SRC_SSA; reg = &ctx->instr[res.num].ssa; } update_range(ctx, reg); return res; } static void set_index(struct ir2_context *ctx, nir_dest * dst, struct ir2_instr *instr) { struct ir2_reg *reg = &instr->ssa; if (dst->is_ssa) { ctx->ssa_map[dst->ssa.index] = instr->idx; } else { assert(instr->is_ssa); reg = &ctx->reg[dst->reg.reg->index]; instr->is_ssa = false; instr->reg = reg; } update_range(ctx, reg); } static struct ir2_instr * ir2_instr_create(struct ir2_context *ctx, int type) { struct ir2_instr *instr; instr = &ctx->instr[ctx->instr_count++]; instr->idx = ctx->instr_count - 1; instr->type = type; instr->block_idx = ctx->block_idx; instr->pred = ctx->pred; instr->is_ssa = true; return instr; } static struct ir2_instr * instr_create_alu(struct ir2_context *ctx, nir_op opcode, unsigned ncomp) { /* emit_alu will fixup instrs that don't map directly */ static const struct ir2_opc { int8_t scalar, vector; } nir_ir2_opc[nir_num_opcodes+1] = { [0 ... nir_num_opcodes - 1] = {-1, -1}, [nir_op_mov] = {MAXs, MAXv}, [nir_op_fneg] = {MAXs, MAXv}, [nir_op_fabs] = {MAXs, MAXv}, [nir_op_fsat] = {MAXs, MAXv}, [nir_op_fsign] = {-1, CNDGTEv}, [nir_op_fadd] = {ADDs, ADDv}, [nir_op_fsub] = {ADDs, ADDv}, [nir_op_fmul] = {MULs, MULv}, [nir_op_ffma] = {-1, MULADDv}, [nir_op_fmax] = {MAXs, MAXv}, [nir_op_fmin] = {MINs, MINv}, [nir_op_ffloor] = {FLOORs, FLOORv}, [nir_op_ffract] = {FRACs, FRACv}, [nir_op_ftrunc] = {TRUNCs, TRUNCv}, [nir_op_fdot2] = {-1, DOT2ADDv}, [nir_op_fdot3] = {-1, DOT3v}, [nir_op_fdot4] = {-1, DOT4v}, [nir_op_sge] = {-1, SETGTEv}, [nir_op_slt] = {-1, SETGTv}, [nir_op_sne] = {-1, SETNEv}, [nir_op_seq] = {-1, SETEv}, [nir_op_fcsel] = {-1, CNDEv}, [nir_op_frsq] = {RECIPSQ_IEEE, -1}, [nir_op_frcp] = {RECIP_IEEE, -1}, [nir_op_flog2] = {LOG_IEEE, -1}, [nir_op_fexp2] = {EXP_IEEE, -1}, [nir_op_fsqrt] = {SQRT_IEEE, -1}, [nir_op_fcos] = {COS, -1}, [nir_op_fsin] = {SIN, -1}, /* no fsat, fneg, fabs since source mods deal with those */ /* so we can use this function with non-nir op */ #define ir2_op_cube nir_num_opcodes [ir2_op_cube] = {-1, CUBEv}, }; struct ir2_opc op = nir_ir2_opc[opcode]; assert(op.vector >= 0 || op.scalar >= 0); struct ir2_instr *instr = ir2_instr_create(ctx, IR2_ALU); instr->alu.vector_opc = op.vector; instr->alu.scalar_opc = op.scalar; instr->alu.export = -1; instr->alu.write_mask = (1 << ncomp) - 1; instr->src_count = opcode == ir2_op_cube ? 2 : nir_op_infos[opcode].num_inputs; instr->ssa.ncomp = ncomp; return instr; } static struct ir2_instr * instr_create_alu_reg(struct ir2_context *ctx, nir_op opcode, uint8_t write_mask, struct ir2_instr *share_reg) { struct ir2_instr *instr; struct ir2_reg *reg; reg = share_reg ? share_reg->reg : &ctx->reg[ctx->reg_count++]; reg->ncomp = MAX2(reg->ncomp, util_logbase2(write_mask) + 1); instr = instr_create_alu(ctx, opcode, util_bitcount(write_mask)); instr->alu.write_mask = write_mask; instr->reg = reg; instr->is_ssa = false; return instr; } static struct ir2_instr * instr_create_alu_dest(struct ir2_context *ctx, nir_op opcode, nir_dest *dst) { struct ir2_instr *instr; instr = instr_create_alu(ctx, opcode, nir_dest_num_components(*dst)); set_index(ctx, dst, instr); return instr; } static struct ir2_instr * ir2_instr_create_fetch(struct ir2_context *ctx, nir_dest *dst, instr_fetch_opc_t opc) { struct ir2_instr *instr = ir2_instr_create(ctx, IR2_FETCH); instr->fetch.opc = opc; instr->src_count = 1; instr->ssa.ncomp = nir_dest_num_components(*dst); set_index(ctx, dst, instr); return instr; } static struct ir2_src make_src_noconst(struct ir2_context *ctx, nir_src src) { struct ir2_instr *instr; if (nir_src_as_const_value(src)) { assert(src.is_ssa); instr = instr_create_alu(ctx, nir_op_mov, src.ssa->num_components); instr->src[0] = make_src(ctx, src); return ir2_src(instr->idx, 0, IR2_SRC_SSA); } return make_src(ctx, src); } static void emit_alu(struct ir2_context *ctx, nir_alu_instr * alu) { const nir_op_info *info = &nir_op_infos[alu->op]; nir_dest *dst = &alu->dest.dest; struct ir2_instr *instr; struct ir2_src tmp; unsigned ncomp; /* get the number of dst components */ if (dst->is_ssa) { ncomp = dst->ssa.num_components; } else { ncomp = 0; for (int i = 0; i < 4; i++) ncomp += !!(alu->dest.write_mask & 1 << i); } instr = instr_create_alu(ctx, alu->op, ncomp); set_index(ctx, dst, instr); instr->alu.saturate = alu->dest.saturate; instr->alu.write_mask = alu->dest.write_mask; for (int i = 0; i < info->num_inputs; i++) { nir_alu_src *src = &alu->src[i]; /* compress swizzle with writemask when applicable */ unsigned swiz = 0, j = 0; for (int i = 0; i < 4; i++) { if (!(alu->dest.write_mask & 1 << i) && !info->output_size) continue; swiz |= swiz_set(src->swizzle[i], j++); } instr->src[i] = make_src(ctx, src->src); instr->src[i].swizzle = swiz_merge(instr->src[i].swizzle, swiz); instr->src[i].negate = src->negate; instr->src[i].abs = src->abs; } /* workarounds for NIR ops that don't map directly to a2xx ops */ switch (alu->op) { case nir_op_fneg: instr->src[0].negate = 1; break; case nir_op_fabs: instr->src[0].abs = 1; break; case nir_op_fsat: instr->alu.saturate = 1; break; case nir_op_slt: tmp = instr->src[0]; instr->src[0] = instr->src[1]; instr->src[1] = tmp; break; case nir_op_fcsel: tmp = instr->src[1]; instr->src[1] = instr->src[2]; instr->src[2] = tmp; break; case nir_op_fsub: instr->src[1].negate = !instr->src[1].negate; break; case nir_op_fdot2: instr->src_count = 3; instr->src[2] = ir2_zero(ctx); break; case nir_op_fsign: { /* we need an extra instruction to deal with the zero case */ struct ir2_instr *tmp; /* tmp = x == 0 ? 0 : 1 */ tmp = instr_create_alu(ctx, nir_op_fcsel, ncomp); tmp->src[0] = instr->src[0]; tmp->src[1] = ir2_zero(ctx); tmp->src[2] = load_const(ctx, (float[]) {1.0f}, 1); /* result = x >= 0 ? tmp : -tmp */ instr->src[1] = ir2_src(tmp->idx, 0, IR2_SRC_SSA); instr->src[2] = instr->src[1]; instr->src[2].negate = true; instr->src_count = 3; } break; default: break; } } static void load_input(struct ir2_context *ctx, nir_dest *dst, unsigned idx) { struct ir2_instr *instr; int slot = -1; if (ctx->so->type == MESA_SHADER_VERTEX) { instr = ir2_instr_create_fetch(ctx, dst, 0); instr->src[0] = ir2_src(0, 0, IR2_SRC_INPUT); instr->fetch.vtx.const_idx = 20 + (idx / 3); instr->fetch.vtx.const_idx_sel = idx % 3; return; } /* get slot from idx */ nir_foreach_variable(var, &ctx->nir->inputs) { if (var->data.driver_location == idx) { slot = var->data.location; break; } } assert(slot >= 0); switch (slot) { case VARYING_SLOT_POS: /* need to extract xy with abs and add tile offset on a20x * zw from fragcoord input (w inverted in fragment shader) * TODO: only components that are required by fragment shader */ instr = instr_create_alu_reg(ctx, ctx->so->is_a20x ? nir_op_fadd : nir_op_mov, 3, NULL); instr->src[0] = ir2_src(ctx->f->inputs_count, 0, IR2_SRC_INPUT); instr->src[0].abs = true; /* on a20x, C64 contains the tile offset */ instr->src[1] = ir2_src(64, 0, IR2_SRC_CONST); instr = instr_create_alu_reg(ctx, nir_op_mov, 4, instr); instr->src[0] = ir2_src(ctx->f->fragcoord, 0, IR2_SRC_INPUT); instr = instr_create_alu_reg(ctx, nir_op_frcp, 8, instr); instr->src[0] = ir2_src(ctx->f->fragcoord, IR2_SWIZZLE_Y, IR2_SRC_INPUT); unsigned reg_idx = instr->reg - ctx->reg; /* XXX */ instr = instr_create_alu_dest(ctx, nir_op_mov, dst); instr->src[0] = ir2_src(reg_idx, 0, IR2_SRC_REG); break; default: instr = instr_create_alu_dest(ctx, nir_op_mov, dst); instr->src[0] = ir2_src(idx, 0, IR2_SRC_INPUT); break; } } static unsigned output_slot(struct ir2_context *ctx, nir_intrinsic_instr *intr) { int slot = -1; unsigned idx = nir_intrinsic_base(intr); nir_foreach_variable(var, &ctx->nir->outputs) { if (var->data.driver_location == idx) { slot = var->data.location; break; } } assert(slot != -1); return slot; } static void store_output(struct ir2_context *ctx, nir_src src, unsigned slot, unsigned ncomp) { struct ir2_instr *instr; unsigned idx = 0; if (ctx->so->type == MESA_SHADER_VERTEX) { switch (slot) { case VARYING_SLOT_POS: ctx->position = make_src(ctx, src); idx = 62; break; case VARYING_SLOT_PSIZ: ctx->so->writes_psize = true; idx = 63; break; default: /* find matching slot from fragment shader input */ for (idx = 0; idx < ctx->f->inputs_count; idx++) if (ctx->f->inputs[idx].slot == slot) break; if (idx == ctx->f->inputs_count) return; } } else if (slot != FRAG_RESULT_COLOR && slot != FRAG_RESULT_DATA0) { /* only color output is implemented */ return; } instr = instr_create_alu(ctx, nir_op_mov, ncomp); instr->src[0] = make_src(ctx, src); instr->alu.export = idx; } static void emit_intrinsic(struct ir2_context *ctx, nir_intrinsic_instr *intr) { struct ir2_instr *instr; nir_const_value *const_offset; unsigned idx; switch (intr->intrinsic) { case nir_intrinsic_load_input: load_input(ctx, &intr->dest, nir_intrinsic_base(intr)); break; case nir_intrinsic_store_output: store_output(ctx, intr->src[0], output_slot(ctx, intr), intr->num_components); break; case nir_intrinsic_load_uniform: const_offset = nir_src_as_const_value(intr->src[0]); assert(const_offset); /* TODO can be false in ES2? */ idx = nir_intrinsic_base(intr); idx += (uint32_t) nir_src_as_const_value(intr->src[0])[0].f32; instr = instr_create_alu_dest(ctx, nir_op_mov, &intr->dest); instr->src[0] = ir2_src(idx, 0, IR2_SRC_CONST); break; case nir_intrinsic_discard: case nir_intrinsic_discard_if: instr = ir2_instr_create(ctx, IR2_ALU); instr->alu.vector_opc = VECTOR_NONE; if (intr->intrinsic == nir_intrinsic_discard_if) { instr->alu.scalar_opc = KILLNEs; instr->src[0] = make_src(ctx, intr->src[0]); } else { instr->alu.scalar_opc = KILLEs; instr->src[0] = ir2_zero(ctx); } instr->alu.export = -1; instr->src_count = 1; ctx->so->has_kill = true; break; case nir_intrinsic_load_front_face: /* gl_FrontFacing is in the sign of param.x * rcp required because otherwise we can't differentiate -0.0 and +0.0 */ ctx->so->need_param = true; struct ir2_instr *tmp = instr_create_alu(ctx, nir_op_frcp, 1); tmp->src[0] = ir2_src(ctx->f->inputs_count, 0, IR2_SRC_INPUT); instr = instr_create_alu_dest(ctx, nir_op_sge, &intr->dest); instr->src[0] = ir2_src(tmp->idx, 0, IR2_SRC_SSA); instr->src[1] = ir2_zero(ctx); break; case nir_intrinsic_load_point_coord: /* param.zw (note: abs might be needed like fragcoord in param.xy?) */ ctx->so->need_param = true; instr = instr_create_alu_dest(ctx, nir_op_mov, &intr->dest); instr->src[0] = ir2_src(ctx->f->inputs_count, IR2_SWIZZLE_ZW, IR2_SRC_INPUT); break; default: compile_error(ctx, "unimplemented intr %d\n", intr->intrinsic); break; } } static void emit_tex(struct ir2_context *ctx, nir_tex_instr * tex) { bool is_rect = false, is_cube = false; struct ir2_instr *instr; nir_src *coord, *lod_bias; coord = lod_bias = NULL; for (unsigned i = 0; i < tex->num_srcs; i++) { switch (tex->src[i].src_type) { case nir_tex_src_coord: coord = &tex->src[i].src; break; case nir_tex_src_bias: case nir_tex_src_lod: assert(!lod_bias); lod_bias = &tex->src[i].src; break; default: compile_error(ctx, "Unhandled NIR tex src type: %d\n", tex->src[i].src_type); return; } } switch (tex->op) { case nir_texop_tex: case nir_texop_txb: case nir_texop_txl: break; default: compile_error(ctx, "unimplemented texop %d\n", tex->op); return; } switch (tex->sampler_dim) { case GLSL_SAMPLER_DIM_2D: break; case GLSL_SAMPLER_DIM_RECT: is_rect = true; break; case GLSL_SAMPLER_DIM_CUBE: is_cube = true; break; default: compile_error(ctx, "unimplemented sampler %d\n", tex->sampler_dim); return; } struct ir2_src src_coord = make_src_noconst(ctx, *coord); /* for cube maps * tmp = cube(coord) * tmp.xy = tmp.xy / |tmp.z| + 1.5 * coord = tmp.xyw */ if (is_cube) { struct ir2_instr *rcp, *coord_xy; unsigned reg_idx; instr = instr_create_alu_reg(ctx, ir2_op_cube, 15, NULL); instr->src[0] = src_coord; instr->src[0].swizzle = IR2_SWIZZLE_ZZXY; instr->src[1] = src_coord; instr->src[1].swizzle = IR2_SWIZZLE_YXZZ; reg_idx = instr->reg - ctx->reg; /* hacky */ rcp = instr_create_alu(ctx, nir_op_frcp, 1); rcp->src[0] = ir2_src(reg_idx, IR2_SWIZZLE_Z, IR2_SRC_REG); rcp->src[0].abs = true; coord_xy = instr_create_alu_reg(ctx, nir_op_ffma, 3, instr); coord_xy->src[0] = ir2_src(reg_idx, 0, IR2_SRC_REG); coord_xy->src[1] = ir2_src(rcp->idx, IR2_SWIZZLE_XXXX, IR2_SRC_SSA); coord_xy->src[2] = load_const(ctx, (float[]) {1.5f}, 1); src_coord = ir2_src(reg_idx, 0, IR2_SRC_REG); /* TODO: lod/bias transformed by src_coord.z ? */ } instr = ir2_instr_create_fetch(ctx, &tex->dest, TEX_FETCH); instr->src[0] = src_coord; instr->src[0].swizzle = is_cube ? IR2_SWIZZLE_YXW : 0; instr->fetch.tex.is_cube = is_cube; instr->fetch.tex.is_rect = is_rect; instr->fetch.tex.samp_id = tex->sampler_index; /* for lod/bias, we insert an extra src for the backend to deal with */ if (lod_bias) { instr->src[1] = make_src_noconst(ctx, *lod_bias); /* backend will use 2-3 components so apply swizzle */ swiz_merge_p(&instr->src[1].swizzle, IR2_SWIZZLE_XXXX); instr->src_count = 2; } } static void setup_input(struct ir2_context *ctx, nir_variable * in) { struct fd2_shader_stateobj *so = ctx->so; unsigned array_len = MAX2(glsl_get_length(in->type), 1); unsigned n = in->data.driver_location; unsigned slot = in->data.location; assert(array_len == 1); /* handle later */ if (ctx->so->type == MESA_SHADER_VERTEX) return; if (ctx->so->type != MESA_SHADER_FRAGMENT) compile_error(ctx, "unknown shader type: %d\n", ctx->so->type); n = ctx->f->inputs_count++; /* half of fragcoord from param reg, half from a varying */ if (slot == VARYING_SLOT_POS) { ctx->f->fragcoord = n; so->need_param = true; } ctx->f->inputs[n].slot = slot; ctx->f->inputs[n].ncomp = glsl_get_components(in->type); /* in->data.interpolation? * opengl ES 2.0 can't do flat mode, but we still get it from GALLIUM_HUD */ } static void emit_undef(struct ir2_context *ctx, nir_ssa_undef_instr * undef) { /* TODO we don't want to emit anything for undefs */ struct ir2_instr *instr; instr = instr_create_alu_dest(ctx, nir_op_mov, &(nir_dest) {.ssa = undef->def,.is_ssa = true}); instr->src[0] = ir2_src(0, 0, IR2_SRC_CONST); } static void emit_instr(struct ir2_context *ctx, nir_instr * instr) { switch (instr->type) { case nir_instr_type_alu: emit_alu(ctx, nir_instr_as_alu(instr)); break; case nir_instr_type_deref: /* ignored, handled as part of the intrinsic they are src to */ break; case nir_instr_type_intrinsic: emit_intrinsic(ctx, nir_instr_as_intrinsic(instr)); break; case nir_instr_type_load_const: /* dealt with when using nir_src */ break; case nir_instr_type_tex: emit_tex(ctx, nir_instr_as_tex(instr)); break; case nir_instr_type_jump: ctx->block_has_jump[ctx->block_idx] = true; break; case nir_instr_type_ssa_undef: emit_undef(ctx, nir_instr_as_ssa_undef(instr)); break; default: break; } } /* fragcoord.zw and a20x hw binning outputs */ static void extra_position_exports(struct ir2_context *ctx, bool binning) { struct ir2_instr *instr, *rcp, *sc, *wincoord, *off; if (ctx->f->fragcoord < 0 && !binning) return; instr = instr_create_alu(ctx, nir_op_fmax, 1); instr->src[0] = ctx->position; instr->src[0].swizzle = IR2_SWIZZLE_W; instr->src[1] = ir2_zero(ctx); rcp = instr_create_alu(ctx, nir_op_frcp, 1); rcp->src[0] = ir2_src(instr->idx, 0, IR2_SRC_SSA); sc = instr_create_alu(ctx, nir_op_fmul, 4); sc->src[0] = ctx->position; sc->src[1] = ir2_src(rcp->idx, IR2_SWIZZLE_XXXX, IR2_SRC_SSA); wincoord = instr_create_alu(ctx, nir_op_ffma, 4); wincoord->src[0] = ir2_src(66, 0, IR2_SRC_CONST); wincoord->src[1] = ir2_src(sc->idx, 0, IR2_SRC_SSA); wincoord->src[2] = ir2_src(65, 0, IR2_SRC_CONST); /* fragcoord z/w */ if (ctx->f->fragcoord >= 0 && !binning) { instr = instr_create_alu(ctx, nir_op_mov, 1); instr->src[0] = ir2_src(wincoord->idx, IR2_SWIZZLE_Z, IR2_SRC_SSA); instr->alu.export = ctx->f->fragcoord; instr = instr_create_alu(ctx, nir_op_mov, 1); instr->src[0] = ctx->position; instr->src[0].swizzle = IR2_SWIZZLE_W; instr->alu.export = ctx->f->fragcoord; instr->alu.write_mask = 2; } if (!binning) return; off = instr_create_alu(ctx, nir_op_fadd, 1); off->src[0] = ir2_src(64, 0, IR2_SRC_CONST); off->src[1] = ir2_src(2, 0, IR2_SRC_INPUT); /* 8 max set in freedreno_screen.. unneeded instrs patched out */ for (int i = 0; i < 8; i++) { instr = instr_create_alu(ctx, nir_op_ffma, 4); instr->src[0] = ir2_src(1, IR2_SWIZZLE_WYWW, IR2_SRC_CONST); instr->src[1] = ir2_src(off->idx, IR2_SWIZZLE_XXXX, IR2_SRC_SSA); instr->src[2] = ir2_src(3 + i, 0, IR2_SRC_CONST); instr->alu.export = 32; instr = instr_create_alu(ctx, nir_op_ffma, 4); instr->src[0] = ir2_src(68 + i * 2, 0, IR2_SRC_CONST); instr->src[1] = ir2_src(wincoord->idx, 0, IR2_SRC_SSA); instr->src[2] = ir2_src(67 + i * 2, 0, IR2_SRC_CONST); instr->alu.export = 33; } } static bool emit_cf_list(struct ir2_context *ctx, struct exec_list *list); static bool emit_block(struct ir2_context *ctx, nir_block * block) { struct ir2_instr *instr; nir_block *succs = block->successors[0]; ctx->block_idx = block->index; nir_foreach_instr(instr, block) emit_instr(ctx, instr); if (!succs || !succs->index) return false; /* we want to be smart and always jump and have the backend cleanup * but we are not, so there are two cases where jump is needed: * loops (succs index lower) * jumps (jump instruction seen in block) */ if (succs->index > block->index && !ctx->block_has_jump[block->index]) return false; assert(block->successors[1] == NULL); instr = ir2_instr_create(ctx, IR2_CF); instr->cf.block_idx = succs->index; /* XXX can't jump to a block with different predicate */ return true; } static void emit_if(struct ir2_context *ctx, nir_if * nif) { unsigned pred = ctx->pred, pred_idx = ctx->pred_idx; struct ir2_instr *instr; /* XXX: blob seems to always use same register for condition */ instr = ir2_instr_create(ctx, IR2_ALU); instr->src[0] = make_src(ctx, nif->condition); instr->src_count = 1; instr->ssa.ncomp = 1; instr->alu.vector_opc = VECTOR_NONE; instr->alu.scalar_opc = SCALAR_NONE; instr->alu.export = -1; instr->alu.write_mask = 1; instr->pred = 0; /* if nested, use PRED_SETNE_PUSHv */ if (pred) { instr->alu.vector_opc = PRED_SETNE_PUSHv; instr->src[1] = instr->src[0]; instr->src[0] = ir2_src(pred_idx, 0, IR2_SRC_SSA); instr->src[0].swizzle = IR2_SWIZZLE_XXXX; instr->src[1].swizzle = IR2_SWIZZLE_XXXX; instr->src_count = 2; } else { instr->alu.scalar_opc = PRED_SETNEs; } ctx->pred_idx = instr->idx; ctx->pred = 3; emit_cf_list(ctx, &nif->then_list); /* TODO: if these is no else branch we don't need this * and if the else branch is simple, can just flip ctx->pred instead */ instr = ir2_instr_create(ctx, IR2_ALU); instr->src[0] = ir2_src(ctx->pred_idx, 0, IR2_SRC_SSA); instr->src_count = 1; instr->ssa.ncomp = 1; instr->alu.vector_opc = VECTOR_NONE; instr->alu.scalar_opc = PRED_SET_INVs; instr->alu.export = -1; instr->alu.write_mask = 1; instr->pred = 0; ctx->pred_idx = instr->idx; emit_cf_list(ctx, &nif->else_list); /* restore predicate for nested predicates */ if (pred) { instr = ir2_instr_create(ctx, IR2_ALU); instr->src[0] = ir2_src(ctx->pred_idx, 0, IR2_SRC_SSA); instr->src_count = 1; instr->ssa.ncomp = 1; instr->alu.vector_opc = VECTOR_NONE; instr->alu.scalar_opc = PRED_SET_POPs; instr->alu.export = -1; instr->alu.write_mask = 1; instr->pred = 0; ctx->pred_idx = instr->idx; } /* restore ctx->pred */ ctx->pred = pred; } /* get the highest block idx in the loop, so we know when * we can free registers that are allocated outside the loop */ static unsigned loop_last_block(struct exec_list *list) { nir_cf_node *node = exec_node_data(nir_cf_node, exec_list_get_tail(list), node); switch (node->type) { case nir_cf_node_block: return nir_cf_node_as_block(node)->index; case nir_cf_node_if: assert(0); /* XXX could this ever happen? */ return 0; case nir_cf_node_loop: return loop_last_block(&nir_cf_node_as_loop(node)->body); default: compile_error(ctx, "Not supported\n"); return 0; } } static void emit_loop(struct ir2_context *ctx, nir_loop *nloop) { ctx->loop_last_block[++ctx->loop_depth] = loop_last_block(&nloop->body); emit_cf_list(ctx, &nloop->body); ctx->loop_depth--; } static bool emit_cf_list(struct ir2_context *ctx, struct exec_list *list) { bool ret = false; foreach_list_typed(nir_cf_node, node, node, list) { ret = false; switch (node->type) { case nir_cf_node_block: ret = emit_block(ctx, nir_cf_node_as_block(node)); break; case nir_cf_node_if: emit_if(ctx, nir_cf_node_as_if(node)); break; case nir_cf_node_loop: emit_loop(ctx, nir_cf_node_as_loop(node)); break; case nir_cf_node_function: compile_error(ctx, "Not supported\n"); break; } } return ret; } static void cleanup_binning(struct ir2_context *ctx) { assert(ctx->so->type == MESA_SHADER_VERTEX); /* kill non-position outputs for binning variant */ nir_foreach_block(block, nir_shader_get_entrypoint(ctx->nir)) { nir_foreach_instr_safe(instr, block) { if (instr->type != nir_instr_type_intrinsic) continue; nir_intrinsic_instr *intr = nir_instr_as_intrinsic(instr); if (intr->intrinsic != nir_intrinsic_store_output) continue; if (output_slot(ctx, intr) != VARYING_SLOT_POS) nir_instr_remove(instr); } } ir2_optimize_nir(ctx->nir, false); } static bool ir2_alu_to_scalar_filter_cb(const nir_instr *instr, const void *data) { if (instr->type != nir_instr_type_alu) return false; nir_alu_instr *alu = nir_instr_as_alu(instr); switch (alu->op) { case nir_op_frsq: case nir_op_frcp: case nir_op_flog2: case nir_op_fexp2: case nir_op_fsqrt: case nir_op_fcos: case nir_op_fsin: return true; default: break; } return false; } void ir2_nir_compile(struct ir2_context *ctx, bool binning) { struct fd2_shader_stateobj *so = ctx->so; memset(ctx->ssa_map, 0xff, sizeof(ctx->ssa_map)); ctx->nir = nir_shader_clone(NULL, so->nir); if (binning) cleanup_binning(ctx); OPT_V(ctx->nir, nir_copy_prop); OPT_V(ctx->nir, nir_opt_dce); OPT_V(ctx->nir, nir_opt_move, nir_move_comparisons); OPT_V(ctx->nir, nir_lower_int_to_float); OPT_V(ctx->nir, nir_lower_bool_to_float); while(OPT(ctx->nir, nir_opt_algebraic)); OPT_V(ctx->nir, nir_opt_algebraic_late); OPT_V(ctx->nir, nir_lower_to_source_mods, nir_lower_all_source_mods); OPT_V(ctx->nir, nir_lower_alu_to_scalar, ir2_alu_to_scalar_filter_cb, NULL); OPT_V(ctx->nir, nir_lower_locals_to_regs); OPT_V(ctx->nir, nir_convert_from_ssa, true); OPT_V(ctx->nir, nir_move_vec_src_uses_to_dest); OPT_V(ctx->nir, nir_lower_vec_to_movs); OPT_V(ctx->nir, nir_opt_dce); nir_sweep(ctx->nir); if (fd_mesa_debug & FD_DBG_DISASM) { debug_printf("----------------------\n"); nir_print_shader(ctx->nir, stdout); debug_printf("----------------------\n"); } /* fd2_shader_stateobj init */ if (so->type == MESA_SHADER_FRAGMENT) { ctx->f->fragcoord = -1; ctx->f->inputs_count = 0; memset(ctx->f->inputs, 0, sizeof(ctx->f->inputs)); } /* Setup inputs: */ nir_foreach_variable(in, &ctx->nir->inputs) setup_input(ctx, in); if (so->type == MESA_SHADER_FRAGMENT) { unsigned idx; for (idx = 0; idx < ctx->f->inputs_count; idx++) { ctx->input[idx].ncomp = ctx->f->inputs[idx].ncomp; update_range(ctx, &ctx->input[idx]); } /* assume we have param input and kill it later if not */ ctx->input[idx].ncomp = 4; update_range(ctx, &ctx->input[idx]); } else { ctx->input[0].ncomp = 1; ctx->input[2].ncomp = 1; update_range(ctx, &ctx->input[0]); update_range(ctx, &ctx->input[2]); } /* And emit the body: */ nir_function_impl *fxn = nir_shader_get_entrypoint(ctx->nir); nir_foreach_register(reg, &fxn->registers) { ctx->reg[reg->index].ncomp = reg->num_components; ctx->reg_count = MAX2(ctx->reg_count, reg->index + 1); } nir_metadata_require(fxn, nir_metadata_block_index); emit_cf_list(ctx, &fxn->body); /* TODO emit_block(ctx, fxn->end_block); */ if (so->type == MESA_SHADER_VERTEX) extra_position_exports(ctx, binning); ralloc_free(ctx->nir); /* kill unused param input */ if (so->type == MESA_SHADER_FRAGMENT && !so->need_param) ctx->input[ctx->f->inputs_count].initialized = false; }