• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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