1 /*
2 * Copyright © 2024 Collabora Ltd.
3 * SPDX-License-Identifier: MIT
4 */
5
6 #include "compiler/nir/nir_builder.h"
7 #include "pan_ir.h"
8
9 /* Mali only provides instructions to fetch varyings with either flat or
10 * perspective-correct interpolation. This pass lowers noperspective varyings
11 * to perspective-correct varyings by multiplying by W in the VS and dividing
12 * by W in the FS.
13 *
14 * This pass needs to lower noperspective varyings in the VS, however Vulkan
15 * and OpenGL do not require interpolation qualifiers to match between stages.
16 * Only the qualifiers in the fragment shader matter. To handle this, we load
17 * a bitfield of noperspective varyings in the linked FS from the
18 * 'noperspective_varyings_pan' sysval in the VS. If the FS qualifiers are
19 * known at compile-time (for example, with monolithic pipelines in vulkan),
20 * this may be lowered to a constant.
21 *
22 * This pass is expected to run after nir_lower_io_to_temporaries and
23 * nir_lower_io, so each IO location must have at most one read or write.
24 * These properties are preserved.
25 *
26 * This pass is expected to run after nir_lower_viewport_transform, so
27 * gl_Position.w is actually 1 / gl_Position.w. This is because
28 * nir_lower_viewport_transform may clamp large W values, and we need to use
29 * the clamped value here. */
30
31 static nir_intrinsic_instr *
find_pos_store(nir_function_impl * impl)32 find_pos_store(nir_function_impl *impl)
33 {
34 /* nir_lower_io_to_temporaries ensures all stores are in the exit block */
35 nir_block *block = nir_impl_last_block(impl);
36 nir_foreach_instr_safe(instr, block) {
37 if (instr->type != nir_instr_type_intrinsic)
38 continue;
39 nir_intrinsic_instr *intrin = nir_instr_as_intrinsic(instr);
40
41 if (intrin->intrinsic != nir_intrinsic_store_output)
42 continue;
43
44 nir_io_semantics sem = nir_intrinsic_io_semantics(intrin);
45 if (sem.location == VARYING_SLOT_POS)
46 return intrin;
47 }
48
49 return NULL;
50 }
51
52 static bool
is_noperspective_load(nir_intrinsic_instr * intrin)53 is_noperspective_load(nir_intrinsic_instr* intrin)
54 {
55 if (intrin->intrinsic != nir_intrinsic_load_interpolated_input)
56 return false;
57
58 nir_intrinsic_instr *bary_instr = nir_src_as_intrinsic(intrin->src[0]);
59 assert(bary_instr);
60
61 return nir_intrinsic_interp_mode(bary_instr) == INTERP_MODE_NOPERSPECTIVE;
62 }
63
64 static bool
has_noperspective_load(nir_function_impl * impl)65 has_noperspective_load(nir_function_impl *impl)
66 {
67 /* nir_lower_io_to_temporaries ersures all loads are in the first block */
68 nir_block *block = nir_start_block(impl);
69 nir_foreach_instr(instr, block) {
70 if (instr->type != nir_instr_type_intrinsic)
71 continue;
72 nir_intrinsic_instr *intrin = nir_instr_as_intrinsic(instr);
73
74 if (is_noperspective_load(intrin))
75 return true;
76 }
77 return false;
78 }
79
80 /**
81 * Returns a bitfield of VS outputs where it is known at compile-time that
82 * noperspective interpolation may be used at runtime. Similar to the
83 * noperspective_varyings_pan sysval, this bitfield only covers user varyings
84 * (starting at VARYING_SLOT_VAR0).
85 *
86 * Precomputed because struct outputs may be split into multiple store_output
87 * intrinsics. If any struct members are integers, then the whole struct
88 * cannot be noperspective.
89 */
90 static uint32_t
get_maybe_noperspective_outputs(nir_function_impl * impl)91 get_maybe_noperspective_outputs(nir_function_impl *impl)
92 {
93 uint32_t used_outputs = 0;
94 uint32_t integer_outputs = 0;
95
96 /* nir_lower_io_to_temporaries ensures all stores are in the exit block */
97 nir_block *block = nir_impl_last_block(impl);
98 nir_foreach_instr_safe(instr, block) {
99 if (instr->type != nir_instr_type_intrinsic)
100 continue;
101 nir_intrinsic_instr *intrin = nir_instr_as_intrinsic(instr);
102
103 if (intrin->intrinsic != nir_intrinsic_store_output)
104 continue;
105
106 nir_io_semantics sem = nir_intrinsic_io_semantics(intrin);
107 if (sem.location < VARYING_SLOT_VAR0)
108 continue;
109
110 uint32_t location_bit = BITFIELD_BIT(sem.location - VARYING_SLOT_VAR0);
111 used_outputs |= location_bit;
112
113 nir_alu_type type = nir_intrinsic_src_type(intrin);
114 nir_alu_type base_type = nir_alu_type_get_base_type(type);
115
116 if (base_type == nir_type_int ||
117 base_type == nir_type_uint ||
118 base_type == nir_type_bool)
119 integer_outputs |= location_bit;
120 }
121
122 /* From the Vulkan 1.1.301 spec:
123 *
124 * "Output attributes of integer or unsigned integer type must always be
125 * flat shaded."
126 *
127 * From the OpenGL 4.6 spec:
128 *
129 * "Implementations need not support interpolation of output values of
130 * integer or unsigned integer type, as all such attributes must be flat
131 * shaded."
132 *
133 * So we can assume varyings that contain integers are never noperspective.
134 */
135 return used_outputs & ~integer_outputs;
136 }
137
138 static bool
is_maybe_noperspective_output(unsigned location,uint32_t maybe_noperspective_outputs)139 is_maybe_noperspective_output(unsigned location,
140 uint32_t maybe_noperspective_outputs)
141 {
142 return location >= VARYING_SLOT_VAR0 &&
143 maybe_noperspective_outputs & BITFIELD_BIT(location - VARYING_SLOT_VAR0);
144 }
145
146 static nir_def *
is_noperspective_output(nir_builder * b,unsigned location,nir_def * noperspective_outputs)147 is_noperspective_output(nir_builder *b, unsigned location,
148 nir_def *noperspective_outputs)
149 {
150 if (location < VARYING_SLOT_VAR0)
151 return nir_imm_bool(b, false);
152 uint32_t bit = BITFIELD_BIT(location - VARYING_SLOT_VAR0);
153 return nir_i2b(b, nir_iand_imm(b, noperspective_outputs, bit));
154 }
155
156 struct lower_noperspective_vs_state {
157 nir_def *pos_w;
158 uint32_t maybe_noperspective_outputs;
159 nir_def *noperspective_outputs;
160 };
161
162 /**
163 * Multiply all noperspective varying stores by gl_Position.w
164 */
165 static bool
lower_noperspective_vs(nir_builder * b,nir_intrinsic_instr * intrin,void * data)166 lower_noperspective_vs(nir_builder *b, nir_intrinsic_instr *intrin,
167 void *data)
168 {
169 struct lower_noperspective_vs_state *state = data;
170
171 if (intrin->intrinsic != nir_intrinsic_store_output)
172 return false;
173 nir_io_semantics sem = nir_intrinsic_io_semantics(intrin);
174
175 if (!is_maybe_noperspective_output(sem.location,
176 state->maybe_noperspective_outputs))
177 return false;
178
179 b->cursor = nir_before_instr(&intrin->instr);
180
181 nir_def *is_noperspective =
182 is_noperspective_output(b, sem.location, state->noperspective_outputs);
183
184 nir_def *old_value = intrin->src[0].ssa;
185 nir_def *noperspective_value = nir_fmul(b, old_value, state->pos_w);
186 nir_def *new_value =
187 nir_bcsel(b, is_noperspective, noperspective_value, old_value);
188
189 nir_src_rewrite(&intrin->src[0], new_value);
190
191 return true;
192 }
193
194 /**
195 * Multiply all noperspective varying loads by gl_FragCoord.w
196 */
197 static bool
lower_noperspective_fs(nir_builder * b,nir_intrinsic_instr * intrin,void * data)198 lower_noperspective_fs(nir_builder *b, nir_intrinsic_instr *intrin,
199 void *data)
200 {
201 if (!is_noperspective_load(intrin))
202 return false;
203
204 b->cursor = nir_after_instr(&intrin->instr);
205
206 nir_def *bary = intrin->src[0].ssa;
207 nir_def *fragcoord_w = nir_load_frag_coord_zw_pan(b, bary, .component = 3);
208
209 nir_def *new_value = nir_fmul(b, &intrin->def, fragcoord_w);
210 nir_def_rewrite_uses_after(&intrin->def, new_value, new_value->parent_instr);
211
212 return true;
213 }
214
215 /**
216 * Move all stores to output variables that occur before the specified
217 * instruction in the same block to after the specified instruction.
218 */
219 static void
move_output_stores_after(nir_instr * after)220 move_output_stores_after(nir_instr *after)
221 {
222 nir_cursor cursor = nir_after_instr(after);
223 nir_block *block = nir_cursor_current_block(cursor);
224 nir_foreach_instr_safe(instr, block) {
225 if (instr == after)
226 break;
227
228 if (instr->type != nir_instr_type_intrinsic)
229 continue;
230 nir_intrinsic_instr *intrin = nir_instr_as_intrinsic(instr);
231
232 if (intrin->intrinsic == nir_intrinsic_store_output)
233 nir_instr_move(cursor, instr);
234 }
235 }
236
237 bool
pan_nir_lower_noperspective_vs(nir_shader * shader)238 pan_nir_lower_noperspective_vs(nir_shader *shader)
239 {
240 assert(shader->info.stage == MESA_SHADER_VERTEX);
241
242 if (!(shader->info.outputs_written & VARYING_BIT_POS))
243 return false;
244
245 nir_function_impl *impl = nir_shader_get_entrypoint(shader);
246
247 uint32_t maybe_noperspective_outputs = get_maybe_noperspective_outputs(impl);
248 if (!maybe_noperspective_outputs)
249 return false;
250
251 nir_intrinsic_instr *pos_store = find_pos_store(impl);
252 assert(pos_store);
253 assert(nir_intrinsic_write_mask(pos_store) & BITFIELD_BIT(3));
254
255 nir_builder b = nir_builder_at(nir_after_instr(&pos_store->instr));
256
257 /* This is after nir_lower_viewport_transform, so stored W is 1/W */
258 nir_def *pos_w_recip = nir_channel(&b, pos_store->src[0].ssa, 3);
259 nir_def *pos_w = nir_frcp(&b, pos_w_recip);
260
261 /* Reorder stores to ensure pos_w def is available */
262 move_output_stores_after(pos_w->parent_instr);
263
264 nir_def *noperspective_outputs = nir_load_noperspective_varyings_pan(&b);
265 struct lower_noperspective_vs_state state = {
266 .pos_w = pos_w,
267 .maybe_noperspective_outputs = maybe_noperspective_outputs,
268 .noperspective_outputs = noperspective_outputs,
269 };
270 nir_shader_intrinsics_pass(shader, lower_noperspective_vs,
271 nir_metadata_control_flow |
272 nir_metadata_loop_analysis,
273 (void *)&state);
274
275 return true;
276 }
277
278 bool
pan_nir_lower_noperspective_fs(nir_shader * shader)279 pan_nir_lower_noperspective_fs(nir_shader *shader)
280 {
281 assert(shader->info.stage == MESA_SHADER_FRAGMENT);
282
283 nir_function_impl *impl = nir_shader_get_entrypoint(shader);
284
285 if (!has_noperspective_load(impl))
286 return false;
287
288 nir_shader_intrinsics_pass(shader, lower_noperspective_fs,
289 nir_metadata_control_flow, NULL);
290
291 return true;
292 }
293
294 static bool
lower_static_noperspective(nir_builder * b,nir_intrinsic_instr * intrin,void * data)295 lower_static_noperspective(nir_builder *b, nir_intrinsic_instr *intrin,
296 void *data)
297 {
298 uint32_t *noperspective_varyings = data;
299
300 if (intrin->intrinsic != nir_intrinsic_load_noperspective_varyings_pan)
301 return false;
302
303 b->cursor = nir_after_instr(&intrin->instr);
304 nir_def *val = nir_imm_int(b, *noperspective_varyings);
305 nir_def_replace(&intrin->def, val);
306
307 return true;
308 }
309
310 /**
311 * Lower loads from the noperspective_varyings_pan sysval to a constant.
312 */
313 bool
pan_nir_lower_static_noperspective(nir_shader * shader,uint32_t noperspective_varyings)314 pan_nir_lower_static_noperspective(nir_shader *shader,
315 uint32_t noperspective_varyings)
316 {
317 return nir_shader_intrinsics_pass(shader, lower_static_noperspective,
318 nir_metadata_control_flow,
319 (void *)&noperspective_varyings);
320 }
321