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
20 * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
21 * IN THE SOFTWARE.
22 *
23 * Authors:
24 *    Rob Clark <robclark@freedesktop.org>
25 */
26
27#include "nir.h"
28#include "nir_builder.h"
29
30#define MAX_CLIP_PLANES 8
31
32/* Generates the lowering code for user-clip-planes, generating CLIPDIST
33 * from UCP[n] + CLIPVERTEX or POSITION.  Additionally, an optional pass
34 * for fragment shaders to insert conditional kills based on the inter-
35 * polated CLIPDIST
36 *
37 * NOTE: should be run after nir_lower_outputs_to_temporaries() (or at
38 * least in scenarios where you can count on each output written once
39 * and only once).
40 */
41
42
43static nir_variable *
44create_clipdist_var(nir_shader *shader, unsigned drvloc,
45                    bool output, gl_varying_slot slot)
46{
47   nir_variable *var = rzalloc(shader, nir_variable);
48
49   var->data.driver_location = drvloc;
50   var->type = glsl_vec4_type();
51   var->data.mode = output ? nir_var_shader_out : nir_var_shader_in;
52   var->name = ralloc_asprintf(var, "clipdist_%d", drvloc);
53   var->data.index = 0;
54   var->data.location = slot;
55
56   if (output) {
57      exec_list_push_tail(&shader->outputs, &var->node);
58      shader->num_outputs++; /* TODO use type_size() */
59   }
60   else {
61      exec_list_push_tail(&shader->inputs, &var->node);
62      shader->num_inputs++;  /* TODO use type_size() */
63   }
64   return var;
65}
66
67static void
68store_clipdist_output(nir_builder *b, nir_variable *out, nir_ssa_def **val)
69{
70   nir_intrinsic_instr *store;
71
72   store = nir_intrinsic_instr_create(b->shader, nir_intrinsic_store_output);
73   store->num_components = 4;
74   nir_intrinsic_set_base(store, out->data.driver_location);
75   nir_intrinsic_set_write_mask(store, 0xf);
76   store->src[0].ssa = nir_vec4(b, val[0], val[1], val[2], val[3]);
77   store->src[0].is_ssa = true;
78   store->src[1] = nir_src_for_ssa(nir_imm_int(b, 0));
79   nir_builder_instr_insert(b, &store->instr);
80}
81
82static void
83load_clipdist_input(nir_builder *b, nir_variable *in, nir_ssa_def **val)
84{
85   nir_intrinsic_instr *load;
86
87   load = nir_intrinsic_instr_create(b->shader, nir_intrinsic_load_input);
88   load->num_components = 4;
89   nir_intrinsic_set_base(load, in->data.driver_location);
90   load->src[0] = nir_src_for_ssa(nir_imm_int(b, 0));
91   nir_ssa_dest_init(&load->instr, &load->dest, 4, 32, NULL);
92   nir_builder_instr_insert(b, &load->instr);
93
94   val[0] = nir_channel(b, &load->dest.ssa, 0);
95   val[1] = nir_channel(b, &load->dest.ssa, 1);
96   val[2] = nir_channel(b, &load->dest.ssa, 2);
97   val[3] = nir_channel(b, &load->dest.ssa, 3);
98}
99
100static nir_ssa_def *
101find_output_in_block(nir_block *block, unsigned drvloc)
102{
103   nir_foreach_instr(instr, block) {
104
105      if (instr->type == nir_instr_type_intrinsic) {
106         nir_intrinsic_instr *intr = nir_instr_as_intrinsic(instr);
107         if ((intr->intrinsic == nir_intrinsic_store_output) &&
108             nir_intrinsic_base(intr) == drvloc) {
109            assert(intr->src[0].is_ssa);
110            assert(nir_src_is_const(intr->src[1]));
111            return intr->src[0].ssa;
112         }
113      }
114   }
115
116   return NULL;
117}
118
119/* TODO: maybe this would be a useful helper?
120 * NOTE: assumes each output is written exactly once (and unconditionally)
121 * so if needed nir_lower_outputs_to_temporaries()
122 */
123static nir_ssa_def *
124find_output(nir_shader *shader, unsigned drvloc)
125{
126   nir_ssa_def *def = NULL;
127   nir_foreach_function(function, shader) {
128      if (function->impl) {
129         nir_foreach_block_reverse(block, function->impl) {
130            nir_ssa_def *new_def = find_output_in_block(block, drvloc);
131            assert(!(new_def && def));
132            def = new_def;
133#if !defined(DEBUG)
134            /* for debug builds, scan entire shader to assert
135             * if output is written multiple times.  For release
136             * builds just assume all is well and bail when we
137             * find first:
138             */
139            if (def)
140               break;
141#endif
142         }
143      }
144   }
145
146   return def;
147}
148
149/*
150 * VS lowering
151 */
152
153/* ucp_enables is bitmask of enabled ucps.  Actual ucp values are
154 * passed in to shader via user_clip_plane system-values
155 *
156 * If use_vars is true, the pass will use variable loads and stores instead
157 * of working with store_output intrinsics.
158 */
159bool
160nir_lower_clip_vs(nir_shader *shader, unsigned ucp_enables, bool use_vars)
161{
162   nir_function_impl *impl = nir_shader_get_entrypoint(shader);
163   nir_ssa_def *clipdist[MAX_CLIP_PLANES];
164   nir_builder b;
165   int maxloc = -1;
166   nir_variable *position = NULL;
167   nir_variable *clipvertex = NULL;
168   nir_ssa_def *cv;
169   nir_variable *out[2] = { NULL };
170
171   if (!ucp_enables)
172      return false;
173
174   nir_builder_init(&b, impl);
175
176   /* NIR should ensure that, even in case of loops/if-else, there
177    * should be only a single predecessor block to end_block, which
178    * makes the perfect place to insert the clipdist calculations.
179    *
180    * NOTE: in case of early returns, these would have to be lowered
181    * to jumps to end_block predecessor in a previous pass.  Not sure
182    * if there is a good way to sanity check this, but for now the
183    * users of this pass don't support sub-routines.
184    */
185   assert(impl->end_block->predecessors->entries == 1);
186   b.cursor = nir_after_cf_list(&impl->body);
187
188   /* find clipvertex/position outputs: */
189   nir_foreach_variable(var, &shader->outputs) {
190      switch (var->data.location) {
191      case VARYING_SLOT_POS:
192         position = var;
193         break;
194      case VARYING_SLOT_CLIP_VERTEX:
195         clipvertex = var;
196         break;
197      case VARYING_SLOT_CLIP_DIST0:
198      case VARYING_SLOT_CLIP_DIST1:
199         /* if shader is already writing CLIPDIST, then
200          * there should be no user-clip-planes to deal
201          * with.
202          *
203          * We assume nir_remove_dead_variables has removed the clipdist
204          * variables if they're not written.
205          */
206         return false;
207      }
208   }
209
210   if (use_vars) {
211      cv = nir_load_var(&b, clipvertex ? clipvertex : position);
212
213      if (clipvertex) {
214         exec_node_remove(&clipvertex->node);
215         clipvertex->data.mode = nir_var_shader_temp;
216         exec_list_push_tail(&shader->globals, &clipvertex->node);
217      }
218   } else {
219      if (clipvertex)
220         cv = find_output(shader, clipvertex->data.driver_location);
221      else if (position)
222         cv = find_output(shader, position->data.driver_location);
223      else
224         return false;
225   }
226
227   /* insert CLIPDIST outputs: */
228   if (ucp_enables & 0x0f)
229      out[0] =
230         create_clipdist_var(shader, ++maxloc, true, VARYING_SLOT_CLIP_DIST0);
231   if (ucp_enables & 0xf0)
232      out[1] =
233         create_clipdist_var(shader, ++maxloc, true, VARYING_SLOT_CLIP_DIST1);
234
235   for (int plane = 0; plane < MAX_CLIP_PLANES; plane++) {
236      if (ucp_enables & (1 << plane)) {
237         nir_ssa_def *ucp = nir_load_user_clip_plane(&b, plane);
238
239         /* calculate clipdist[plane] - dot(ucp, cv): */
240         clipdist[plane] = nir_fdot4(&b, ucp, cv);
241      } else {
242         /* 0.0 == don't-clip == disabled: */
243         clipdist[plane] = nir_imm_float(&b, 0.0);
244      }
245   }
246
247   if (use_vars) {
248      if (ucp_enables & 0x0f)
249         nir_store_var(&b, out[0], nir_vec(&b, clipdist, 4), 0xf);
250      if (ucp_enables & 0xf0)
251         nir_store_var(&b, out[1], nir_vec(&b, &clipdist[4], 4), 0xf);
252   } else {
253      if (ucp_enables & 0x0f)
254         store_clipdist_output(&b, out[0], &clipdist[0]);
255      if (ucp_enables & 0xf0)
256         store_clipdist_output(&b, out[1], &clipdist[4]);
257   }
258
259   nir_metadata_preserve(impl, nir_metadata_dominance);
260
261   return true;
262}
263
264/*
265 * FS lowering
266 */
267
268static void
269lower_clip_fs(nir_function_impl *impl, unsigned ucp_enables,
270              nir_variable **in)
271{
272   nir_ssa_def *clipdist[MAX_CLIP_PLANES];
273   nir_builder b;
274
275   nir_builder_init(&b, impl);
276   b.cursor = nir_before_cf_list(&impl->body);
277
278   if (ucp_enables & 0x0f)
279      load_clipdist_input(&b, in[0], &clipdist[0]);
280   if (ucp_enables & 0xf0)
281      load_clipdist_input(&b, in[1], &clipdist[4]);
282
283   for (int plane = 0; plane < MAX_CLIP_PLANES; plane++) {
284      if (ucp_enables & (1 << plane)) {
285         nir_intrinsic_instr *discard;
286         nir_ssa_def *cond;
287
288         cond = nir_flt(&b, clipdist[plane], nir_imm_float(&b, 0.0));
289
290         discard = nir_intrinsic_instr_create(b.shader,
291                                              nir_intrinsic_discard_if);
292         discard->src[0] = nir_src_for_ssa(cond);
293         nir_builder_instr_insert(&b, &discard->instr);
294
295         b.shader->info.fs.uses_discard = true;
296      }
297   }
298
299   nir_metadata_preserve(impl, nir_metadata_dominance);
300}
301
302/* insert conditional kill based on interpolated CLIPDIST
303 */
304bool
305nir_lower_clip_fs(nir_shader *shader, unsigned ucp_enables)
306{
307   nir_variable *in[2];
308   int maxloc = -1;
309
310   if (!ucp_enables)
311      return false;
312
313   nir_foreach_variable(var, &shader->inputs) {
314      int loc = var->data.driver_location;
315
316      /* keep track of last used driver-location.. we'll be
317       * appending CLIP_DIST0/CLIP_DIST1 after last existing
318       * input:
319       */
320      maxloc = MAX2(maxloc, loc);
321   }
322
323   /* The shader won't normally have CLIPDIST inputs, so we
324    * must add our own:
325    */
326   /* insert CLIPDIST outputs: */
327   if (ucp_enables & 0x0f)
328      in[0] =
329         create_clipdist_var(shader, ++maxloc, false,
330                             VARYING_SLOT_CLIP_DIST0);
331   if (ucp_enables & 0xf0)
332      in[1] =
333         create_clipdist_var(shader, ++maxloc, false,
334                             VARYING_SLOT_CLIP_DIST1);
335
336   nir_foreach_function(function, shader) {
337      if (!strcmp(function->name, "main"))
338         lower_clip_fs(function->impl, ucp_enables, in);
339   }
340
341   return true;
342}
343