1 /*
2 * Copyright © 2015 Red Hat
3 *
4 * Permission is hereby granted, free of charge, to any person obtaining a
5 * copy of this software and associated documentation files (the "Software"),
6 * to deal in the Software without restriction, including without limitation
7 * the rights to use, copy, modify, merge, publish, distribute, sublicense,
8 * and/or sell copies of the Software, and to permit persons to whom the
9 * Software is furnished to do so, subject to the following conditions:
10 *
11 * The above copyright notice and this permission notice (including the next
12 * paragraph) shall be included in all copies or substantial portions of the
13 * Software.
14 *
15 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
18 * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20 * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21 * SOFTWARE.
22 */
23
24 #include "nir.h"
25 #include "nir_builder.h"
26
27 /* Lower gl_FragCoord (and fddy) to account for driver's requested coordinate-
28 * origin and pixel-center vs. shader. If transformation is required, a
29 * gl_FbWposYTransform uniform is inserted (with the specified state-slots)
30 * and additional instructions are inserted to transform gl_FragCoord (and
31 * fddy src arg).
32 *
33 * This is based on the logic in emit_wpos()/emit_wpos_adjustment() in TGSI
34 * compiler.
35 */
36
37 typedef struct {
38 const nir_lower_wpos_ytransform_options *options;
39 nir_shader *shader;
40 nir_builder b;
41 nir_variable *transform;
42 } lower_wpos_ytransform_state;
43
44 static nir_def *
get_transform(lower_wpos_ytransform_state * state)45 get_transform(lower_wpos_ytransform_state *state)
46 {
47 if (state->transform == NULL) {
48 /* NOTE: name must be prefixed w/ "gl_" to trigger slot based
49 * special handling in uniform setup:
50 */
51 nir_variable *var = nir_state_variable_create(state->shader,
52 glsl_vec4_type(),
53 "gl_FbWposYTransform",
54 state->options->state_tokens);
55
56 var->data.how_declared = nir_var_hidden;
57 state->transform = var;
58 }
59 return nir_load_var(&state->b, state->transform);
60 }
61
62 /* NIR equiv of TGSI CMP instruction: */
63 static nir_def *
nir_cmp(nir_builder * b,nir_def * src0,nir_def * src1,nir_def * src2)64 nir_cmp(nir_builder *b, nir_def *src0, nir_def *src1, nir_def *src2)
65 {
66 return nir_bcsel(b, nir_flt_imm(b, src0, 0.0), src1, src2);
67 }
68
69 /* see emit_wpos_adjustment() in st_mesa_to_tgsi.c */
70 static void
emit_wpos_adjustment(lower_wpos_ytransform_state * state,nir_intrinsic_instr * intr,bool invert,float adjX,float adjY[2])71 emit_wpos_adjustment(lower_wpos_ytransform_state *state,
72 nir_intrinsic_instr *intr, bool invert,
73 float adjX, float adjY[2])
74 {
75 nir_builder *b = &state->b;
76 nir_def *wpostrans, *wpos_temp, *wpos_temp_y, *wpos_input;
77
78 wpos_input = &intr->def;
79
80 b->cursor = nir_after_instr(&intr->instr);
81
82 wpostrans = get_transform(state);
83
84 /* First, apply the coordinate shift: */
85 if (adjX || adjY[0] || adjY[1]) {
86 if (adjY[0] != adjY[1]) {
87 /* Adjust the y coordinate by adjY[1] or adjY[0] respectively
88 * depending on whether inversion is actually going to be applied
89 * or not, which is determined by testing against the inversion
90 * state variable used below, which will be either +1 or -1.
91 */
92 nir_def *adj_temp;
93
94 adj_temp = nir_cmp(b,
95 nir_channel(b, wpostrans, invert ? 2 : 0),
96 nir_imm_vec4(b, adjX, adjY[0], 0.0f, 0.0f),
97 nir_imm_vec4(b, adjX, adjY[1], 0.0f, 0.0f));
98
99 wpos_temp = nir_fadd(b, wpos_input, adj_temp);
100 } else {
101 wpos_temp = nir_fadd(b,
102 wpos_input,
103 nir_imm_vec4(b, adjX, adjY[0], 0.0f, 0.0f));
104 }
105 wpos_input = wpos_temp;
106 } else {
107 /* MOV wpos_temp, input[wpos]
108 */
109 wpos_temp = wpos_input;
110 }
111
112 /* Now the conditional y flip: STATE_FB_WPOS_Y_TRANSFORM.xy/zw will be
113 * inversion/identity, or the other way around if we're drawing to an FBO.
114 */
115 if (invert) {
116 /* wpos_temp.y = wpos_input * wpostrans.xxxx + wpostrans.yyyy */
117 wpos_temp_y = nir_fadd(b, nir_fmul(b, nir_channel(b, wpos_temp, 1), nir_channel(b, wpostrans, 0)),
118 nir_channel(b, wpostrans, 1));
119 } else {
120 /* wpos_temp.y = wpos_input * wpostrans.zzzz + wpostrans.wwww */
121 wpos_temp_y = nir_fadd(b, nir_fmul(b, nir_channel(b, wpos_temp, 1), nir_channel(b, wpostrans, 2)),
122 nir_channel(b, wpostrans, 3));
123 }
124
125 wpos_temp = nir_vec4(b,
126 nir_channel(b, wpos_temp, 0),
127 wpos_temp_y,
128 nir_channel(b, wpos_temp, 2),
129 nir_channel(b, wpos_temp, 3));
130
131 nir_def_rewrite_uses_after(&intr->def,
132 wpos_temp,
133 wpos_temp->parent_instr);
134 }
135
136 static void
lower_fragcoord(lower_wpos_ytransform_state * state,nir_intrinsic_instr * intr)137 lower_fragcoord(lower_wpos_ytransform_state *state, nir_intrinsic_instr *intr)
138 {
139 const nir_lower_wpos_ytransform_options *options = state->options;
140 float adjX = 0.0f;
141 float adjY[2] = { 0.0f, 0.0f };
142 bool invert = false;
143
144 /* Based on logic in emit_wpos():
145 *
146 * Query the pixel center conventions supported by the pipe driver and set
147 * adjX, adjY to help out if it cannot handle the requested one internally.
148 *
149 * The bias of the y-coordinate depends on whether y-inversion takes place
150 * (adjY[1]) or not (adjY[0]), which is in turn dependent on whether we are
151 * drawing to an FBO (causes additional inversion), and whether the pipe
152 * driver origin and the requested origin differ (the latter condition is
153 * stored in the 'invert' variable).
154 *
155 * For height = 100 (i = integer, h = half-integer, l = lower, u = upper):
156 *
157 * center shift only:
158 * i -> h: +0.5
159 * h -> i: -0.5
160 *
161 * inversion only:
162 * l,i -> u,i: ( 0.0 + 1.0) * -1 + 100 = 99
163 * l,h -> u,h: ( 0.5 + 0.0) * -1 + 100 = 99.5
164 * u,i -> l,i: (99.0 + 1.0) * -1 + 100 = 0
165 * u,h -> l,h: (99.5 + 0.0) * -1 + 100 = 0.5
166 *
167 * inversion and center shift:
168 * l,i -> u,h: ( 0.0 + 0.5) * -1 + 100 = 99.5
169 * l,h -> u,i: ( 0.5 + 0.5) * -1 + 100 = 99
170 * u,i -> l,h: (99.0 + 0.5) * -1 + 100 = 0.5
171 * u,h -> l,i: (99.5 + 0.5) * -1 + 100 = 0
172 */
173
174 if (state->shader->info.fs.origin_upper_left) {
175 /* Fragment shader wants origin in upper-left */
176 if (options->fs_coord_origin_upper_left) {
177 /* the driver supports upper-left origin */
178 } else if (options->fs_coord_origin_lower_left) {
179 /* the driver supports lower-left origin, need to invert Y */
180 invert = true;
181 } else {
182 unreachable("invalid options");
183 }
184 } else {
185 /* Fragment shader wants origin in lower-left */
186 if (options->fs_coord_origin_lower_left) {
187 /* the driver supports lower-left origin */
188 } else if (options->fs_coord_origin_upper_left) {
189 /* the driver supports upper-left origin, need to invert Y */
190 invert = true;
191 } else {
192 unreachable("invalid options");
193 }
194 }
195
196 if (state->shader->info.fs.pixel_center_integer) {
197 /* Fragment shader wants pixel center integer */
198 if (options->fs_coord_pixel_center_integer) {
199 /* the driver supports pixel center integer */
200 adjY[1] = 1.0f;
201 } else if (options->fs_coord_pixel_center_half_integer) {
202 /* the driver supports pixel center half integer, need to bias X,Y */
203 adjX = -0.5f;
204 adjY[0] = -0.5f;
205 adjY[1] = 0.5f;
206 } else {
207 unreachable("invalid options");
208 }
209 } else {
210 /* Fragment shader wants pixel center half integer */
211 if (options->fs_coord_pixel_center_half_integer) {
212 /* the driver supports pixel center half integer */
213 } else if (options->fs_coord_pixel_center_integer) {
214 /* the driver supports pixel center integer, need to bias X,Y */
215 adjX = adjY[0] = adjY[1] = 0.5f;
216 } else {
217 unreachable("invalid options");
218 }
219 }
220
221 emit_wpos_adjustment(state, intr, invert, adjX, adjY);
222 }
223
224 /* turns 'fddy(p)' into 'fddy(fmul(p, transform.x))' */
225 static void
lower_fddy(lower_wpos_ytransform_state * state,nir_alu_instr * fddy)226 lower_fddy(lower_wpos_ytransform_state *state, nir_alu_instr *fddy)
227 {
228 nir_builder *b = &state->b;
229 nir_def *p, *pt, *trans;
230
231 b->cursor = nir_before_instr(&fddy->instr);
232
233 p = nir_ssa_for_alu_src(b, fddy, 0);
234 trans = nir_channel(b, get_transform(state), 0);
235 if (p->bit_size == 16)
236 trans = nir_f2f16(b, trans);
237
238 pt = nir_fmul(b, p, trans);
239
240 nir_src_rewrite(&fddy->src[0].src, pt);
241
242 for (unsigned i = 0; i < 4; i++)
243 fddy->src[0].swizzle[i] = MIN2(i, pt->num_components - 1);
244 }
245
246 /* Multiply interp_deref_at_offset's or load_barycentric_at_offset's offset
247 * by transform.x to flip it.
248 */
249 static void
lower_interp_deref_or_load_baryc_at_offset(lower_wpos_ytransform_state * state,nir_intrinsic_instr * intr,unsigned offset_src)250 lower_interp_deref_or_load_baryc_at_offset(lower_wpos_ytransform_state *state,
251 nir_intrinsic_instr *intr,
252 unsigned offset_src)
253 {
254 nir_builder *b = &state->b;
255 nir_def *offset;
256 nir_def *flip_y;
257
258 b->cursor = nir_before_instr(&intr->instr);
259
260 offset = intr->src[offset_src].ssa;
261 flip_y = nir_fmul(b, nir_channel(b, offset, 1),
262 nir_channel(b, get_transform(state), 0));
263 nir_src_rewrite(&intr->src[offset_src],
264 nir_vec2(b, nir_channel(b, offset, 0), flip_y));
265 }
266
267 static void
lower_load_sample_pos(lower_wpos_ytransform_state * state,nir_intrinsic_instr * intr)268 lower_load_sample_pos(lower_wpos_ytransform_state *state,
269 nir_intrinsic_instr *intr)
270 {
271 nir_builder *b = &state->b;
272 b->cursor = nir_after_instr(&intr->instr);
273
274 nir_def *pos = &intr->def;
275 nir_def *scale = nir_channel(b, get_transform(state), 0);
276 nir_def *neg_scale = nir_channel(b, get_transform(state), 2);
277 /* Either y or 1-y for scale equal to 1 or -1 respectively. */
278 nir_def *flipped_y =
279 nir_fadd(b, nir_fmax(b, neg_scale, nir_imm_float(b, 0.0)),
280 nir_fmul(b, nir_channel(b, pos, 1), scale));
281 nir_def *flipped_pos = nir_vec2(b, nir_channel(b, pos, 0), flipped_y);
282
283 nir_def_rewrite_uses_after(&intr->def, flipped_pos,
284 flipped_pos->parent_instr);
285 }
286
287 static bool
lower_wpos_ytransform_instr(nir_builder * b,nir_instr * instr,void * data)288 lower_wpos_ytransform_instr(nir_builder *b, nir_instr *instr,
289 void *data)
290 {
291 lower_wpos_ytransform_state *state = data;
292 state->b = *b;
293
294 if (instr->type == nir_instr_type_intrinsic) {
295 nir_intrinsic_instr *intr = nir_instr_as_intrinsic(instr);
296 if (intr->intrinsic == nir_intrinsic_load_deref) {
297 nir_deref_instr *deref = nir_src_as_deref(intr->src[0]);
298 nir_variable *var = nir_deref_instr_get_variable(deref);
299 if ((var->data.mode == nir_var_shader_in &&
300 var->data.location == VARYING_SLOT_POS) ||
301 (var->data.mode == nir_var_system_value &&
302 var->data.location == SYSTEM_VALUE_FRAG_COORD)) {
303 /* gl_FragCoord should not have array/struct derefs: */
304 lower_fragcoord(state, intr);
305 } else if (var->data.mode == nir_var_system_value &&
306 var->data.location == SYSTEM_VALUE_SAMPLE_POS) {
307 lower_load_sample_pos(state, intr);
308 }
309 } else if (intr->intrinsic == nir_intrinsic_load_frag_coord) {
310 lower_fragcoord(state, intr);
311 } else if (intr->intrinsic == nir_intrinsic_load_sample_pos) {
312 lower_load_sample_pos(state, intr);
313 } else if (intr->intrinsic == nir_intrinsic_interp_deref_at_offset) {
314 lower_interp_deref_or_load_baryc_at_offset(state, intr, 1);
315 } else if (intr->intrinsic == nir_intrinsic_load_barycentric_at_offset) {
316 lower_interp_deref_or_load_baryc_at_offset(state, intr, 0);
317 }
318 } else if (instr->type == nir_instr_type_alu) {
319 nir_alu_instr *alu = nir_instr_as_alu(instr);
320 if (alu->op == nir_op_fddy ||
321 alu->op == nir_op_fddy_fine ||
322 alu->op == nir_op_fddy_coarse)
323 lower_fddy(state, alu);
324 }
325
326 return state->transform != NULL;
327 }
328
329 bool
nir_lower_wpos_ytransform(nir_shader * shader,const nir_lower_wpos_ytransform_options * options)330 nir_lower_wpos_ytransform(nir_shader *shader,
331 const nir_lower_wpos_ytransform_options *options)
332 {
333 lower_wpos_ytransform_state state = {
334 .options = options,
335 .shader = shader,
336 };
337
338 assert(shader->info.stage == MESA_SHADER_FRAGMENT);
339
340 return nir_shader_instructions_pass(shader,
341 lower_wpos_ytransform_instr,
342 nir_metadata_block_index |
343 nir_metadata_dominance,
344 &state);
345 }
346