/* * Copyright (c) 2025 Intel Corporation * SPDX-License-Identifier: MIT */ #include "compiler/nir/nir_builder.h" #include "brw_nir.h" static nir_def * load_input_vertex(struct nir_builder *b, nir_intrinsic_instr *intrin, int vtx_index, uint8_t num_components) { return nir_load_input_vertex(b, num_components, intrin->def.bit_size, nir_imm_int(b, vtx_index), intrin->src[0].ssa, .base = nir_intrinsic_base(intrin), .component = nir_intrinsic_component(intrin), /* No dest_type means it's fs_interp_deltas, * and then we just want floats. */ .dest_type = nir_intrinsic_has_dest_type(intrin) ? nir_intrinsic_dest_type(intrin) : nir_type_float | intrin->def.bit_size, .io_semantics = nir_intrinsic_io_semantics(intrin)); } /* If an input is marked for constant interpolation, the HW will copy the * value of the provoking vertex to all components in the FS payload. * However, due to the way we have to program the provoking vertex state * to respect the order in which Vulkan says the per-vertex values should * come, we cannot count on that value being correct. * To work around that, we convert any load_input into a load_input_vertex * for the corresponding vertex index depending on the provoking vertex value. */ static bool lower_flat_inputs(nir_builder *b, nir_intrinsic_instr *intrin, void *data) { if (intrin->intrinsic != nir_intrinsic_load_input) return false; b->cursor = nir_after_instr(&intrin->instr); nir_def *first_vtx = load_input_vertex(b, intrin, 0, intrin->def.num_components); nir_def *last_vtx = load_input_vertex(b, intrin, 2, intrin->def.num_components); nir_def *last = nir_test_fs_config_intel( b, 1, INTEL_FS_CONFIG_PROVOKING_VERTEX_LAST); nir_def *input_vertex = nir_bcsel(b, last, last_vtx, first_vtx); nir_def_replace(&intrin->def, input_vertex); return true; } static nir_def * get_bary_deltas(nir_builder *b, nir_intrinsic_instr *bary, nir_intrinsic_op op, unsigned interp_mode) { nir_def *deltas; switch (op) { case nir_intrinsic_load_barycentric_pixel: case nir_intrinsic_load_barycentric_centroid: case nir_intrinsic_load_barycentric_sample: deltas = nir_load_barycentric(b, op, interp_mode); break; case nir_intrinsic_load_barycentric_at_sample: deltas = nir_load_barycentric_at_sample(b, bary->def.bit_size, bary->src[0].ssa, .interp_mode = interp_mode); break; case nir_intrinsic_load_barycentric_at_offset: deltas = nir_load_barycentric_at_offset(b, bary->def.bit_size, bary->src[0].ssa, .interp_mode = interp_mode); break; default: UNREACHABLE("invalid barycentric op"); } return deltas; } /* Lower the coord version of bary intrinsics to the non-coord version, * then calculate the three components from the assumption they all add * up to 1.0. */ static bool lower_coord_barycentrics(nir_builder *b, nir_intrinsic_instr *intrin, void *data) { nir_intrinsic_op op; switch (intrin->intrinsic) { case nir_intrinsic_load_barycentric_coord_pixel: op = nir_intrinsic_load_barycentric_pixel; break; case nir_intrinsic_load_barycentric_coord_centroid: op = nir_intrinsic_load_barycentric_centroid; break; case nir_intrinsic_load_barycentric_coord_sample: op = nir_intrinsic_load_barycentric_sample; break; case nir_intrinsic_load_barycentric_coord_at_sample: op = nir_intrinsic_load_barycentric_at_sample; break; case nir_intrinsic_load_barycentric_coord_at_offset: op = nir_intrinsic_load_barycentric_at_offset; break; default: return false; } b->cursor = nir_after_instr(&intrin->instr); nir_def *delta_xy = get_bary_deltas(b, intrin, op, nir_intrinsic_interp_mode(intrin)); nir_def *barys[3], *res; barys[0] = nir_fsub_imm(b, 1.0f, nir_fadd(b, nir_channel(b, delta_xy, 0), nir_channel(b, delta_xy, 1))); barys[1] = nir_channel(b, delta_xy, 0); barys[2] = nir_channel(b, delta_xy, 1); res = nir_vec(b, barys, 3); nir_def_replace(&intrin->def, res); return true; } /* The HW can give us the interpolation deltas for inputs, or the per-vertex * values, but it does not mix them. If we have any per-vertex inputs, we need * to calculate the deltas for any old fashioned interpolated values * ourselves. */ static bool lower_fs_interp_deltas(nir_builder *b, nir_intrinsic_instr *intrin, void *data) { if (intrin->intrinsic != nir_intrinsic_load_fs_input_interp_deltas) return false; b->cursor = nir_after_instr(&intrin->instr); nir_def *vertices[3] = { load_input_vertex(b, intrin, 0, 1), load_input_vertex(b, intrin, 2, 1), load_input_vertex(b, intrin, 1, 1), }; nir_def *deltas[3] = { vertices[0], nir_fsub(b, vertices[1], vertices[0]), nir_fsub(b, vertices[2], vertices[0]), }; nir_def *vec = nir_vec(b, deltas, 3); nir_def_replace(&intrin->def, vec); return true; } void brw_nir_lower_fs_barycentrics(nir_shader *shader) { NIR_PASS(_, shader, nir_shader_intrinsics_pass, lower_flat_inputs, nir_metadata_control_flow, NULL); NIR_PASS(_, shader, nir_shader_intrinsics_pass, lower_coord_barycentrics, nir_metadata_control_flow, NULL); NIR_PASS(_, shader, nir_shader_intrinsics_pass, lower_fs_interp_deltas, nir_metadata_control_flow, NULL); /* If there were any flat inputs, we lowered to per-vertex, * so change the interpolation mode here to avoid setting * them up for constant interpolation in the SBE setup. */ nir_foreach_shader_in_variable(var, shader) { if (var->data.interpolation == INTERP_MODE_FLAT) { var->data.interpolation = INTERP_MODE_SMOOTH; } } }