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 (at) 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 43 static nir_variable * 44 create_clipdist_var(nir_shader *shader, 45 bool output, gl_varying_slot slot, unsigned array_size) 46 { 47 nir_variable *var = rzalloc(shader, nir_variable); 48 49 /* TODO use type_size() for num_inputs/outputs */ 50 if (output) { 51 var->data.driver_location = shader->num_outputs++; 52 var->data.mode = nir_var_shader_out; 53 } else { 54 var->data.driver_location = shader->num_inputs++; 55 var->data.mode = nir_var_shader_in; 56 } 57 var->name = ralloc_asprintf(var, "clipdist_%d", var->data.driver_location); 58 var->data.index = 0; 59 var->data.location = slot; 60 61 if (array_size > 0) { 62 var->type = glsl_array_type(glsl_float_type(), array_size, 63 sizeof(float)); 64 var->data.compact = 1; 65 } else 66 var->type = glsl_vec4_type(); 67 68 nir_shader_add_variable(shader, var); 69 return var; 70 } 71 72 static void 73 create_clipdist_vars(nir_shader *shader, nir_variable **io_vars, 74 unsigned ucp_enables, bool output, 75 bool use_clipdist_array) 76 { 77 if (use_clipdist_array) { 78 shader->info.clip_distance_array_size = util_last_bit(ucp_enables); 79 io_vars[0] = 80 create_clipdist_var(shader, output, 81 VARYING_SLOT_CLIP_DIST0, 82 shader->info.clip_distance_array_size); 83 } else { 84 if (ucp_enables & 0x0f) 85 io_vars[0] = 86 create_clipdist_var(shader, output, 87 VARYING_SLOT_CLIP_DIST0, 0); 88 if (ucp_enables & 0xf0) 89 io_vars[1] = 90 create_clipdist_var(shader, output, 91 VARYING_SLOT_CLIP_DIST1, 0); 92 } 93 } 94 95 static void 96 store_clipdist_output(nir_builder *b, nir_variable *out, nir_ssa_def **val) 97 { 98 nir_io_semantics semantics = { 99 .location = out->data.location, 100 .num_slots = 1, 101 }; 102 103 nir_store_output(b, nir_vec4(b, val[0], val[1], val[2], val[3]), nir_imm_int(b, 0), 104 .base = out->data.driver_location, 105 .write_mask = 0xf, 106 .io_semantics = semantics); 107 } 108 109 static void 110 load_clipdist_input(nir_builder *b, nir_variable *in, int location_offset, 111 nir_ssa_def **val) 112 { 113 nir_io_semantics semantics = { 114 .location = in->data.location, 115 .num_slots = 1, 116 }; 117 118 nir_ssa_def *load = 119 nir_load_input(b, 4, 32, nir_imm_int(b, 0), 120 .base = in->data.driver_location + location_offset, 121 .io_semantics = semantics); 122 123 val[0] = nir_channel(b, load, 0); 124 val[1] = nir_channel(b, load, 1); 125 val[2] = nir_channel(b, load, 2); 126 val[3] = nir_channel(b, load, 3); 127 } 128 129 static nir_ssa_def * 130 find_output_in_block(nir_block *block, unsigned drvloc) 131 { 132 nir_foreach_instr(instr, block) { 133 134 if (instr->type == nir_instr_type_intrinsic) { 135 nir_intrinsic_instr *intr = nir_instr_as_intrinsic(instr); 136 if ((intr->intrinsic == nir_intrinsic_store_output) && 137 nir_intrinsic_base(intr) == drvloc) { 138 assert(intr->src[0].is_ssa); 139 assert(nir_src_is_const(intr->src[1])); 140 return intr->src[0].ssa; 141 } 142 } 143 } 144 145 return NULL; 146 } 147 148 /* TODO: maybe this would be a useful helper? 149 * NOTE: assumes each output is written exactly once (and unconditionally) 150 * so if needed nir_lower_outputs_to_temporaries() 151 */ 152 static nir_ssa_def * 153 find_output(nir_shader *shader, unsigned drvloc) 154 { 155 nir_ssa_def *def = NULL; 156 nir_foreach_function(function, shader) { 157 if (function->impl) { 158 nir_foreach_block_reverse(block, function->impl) { 159 nir_ssa_def *new_def = find_output_in_block(block, drvloc); 160 assert(!(new_def && def)); 161 def = new_def; 162 #if !defined(DEBUG) 163 /* for debug builds, scan entire shader to assert 164 * if output is written multiple times. For release 165 * builds just assume all is well and bail when we 166 * find first: 167 */ 168 if (def) 169 break; 170 #endif 171 } 172 } 173 } 174 175 return def; 176 } 177 178 static bool 179 find_clipvertex_and_position_outputs(nir_shader *shader, 180 nir_variable **clipvertex, 181 nir_variable **position) 182 { 183 nir_foreach_shader_out_variable(var, shader) { 184 switch (var->data.location) { 185 case VARYING_SLOT_POS: 186 *position = var; 187 break; 188 case VARYING_SLOT_CLIP_VERTEX: 189 *clipvertex = var; 190 break; 191 case VARYING_SLOT_CLIP_DIST0: 192 case VARYING_SLOT_CLIP_DIST1: 193 /* if shader is already writing CLIPDIST, then 194 * there should be no user-clip-planes to deal 195 * with. 196 * 197 * We assume nir_remove_dead_variables has removed the clipdist 198 * variables if they're not written. 199 */ 200 return false; 201 } 202 } 203 204 return *clipvertex || *position; 205 } 206 207 static nir_ssa_def * 208 get_ucp(nir_builder *b, int plane, 209 const gl_state_index16 clipplane_state_tokens[][STATE_LENGTH]) 210 { 211 if (clipplane_state_tokens) { 212 char tmp[100]; 213 snprintf(tmp, ARRAY_SIZE(tmp), "gl_ClipPlane%dMESA", plane); 214 nir_variable *var = nir_variable_create(b->shader, 215 nir_var_uniform, 216 glsl_vec4_type(), 217 tmp); 218 219 var->num_state_slots = 1; 220 var->state_slots = ralloc_array(var, nir_state_slot, 1); 221 memcpy(var->state_slots[0].tokens, 222 clipplane_state_tokens[plane], 223 sizeof(var->state_slots[0].tokens)); 224 return nir_load_var(b, var); 225 } else 226 return nir_load_user_clip_plane(b, plane); 227 } 228 229 230 static void 231 lower_clip_outputs(nir_builder *b, nir_variable *position, 232 nir_variable *clipvertex, nir_variable **out, 233 unsigned ucp_enables, bool use_vars, 234 bool use_clipdist_array, 235 const gl_state_index16 clipplane_state_tokens[][STATE_LENGTH]) 236 { 237 nir_ssa_def *clipdist[MAX_CLIP_PLANES]; 238 nir_ssa_def *cv; 239 240 if (use_vars) { 241 cv = nir_load_var(b, clipvertex ? clipvertex : position); 242 243 if (clipvertex) { 244 clipvertex->data.mode = nir_var_shader_temp; 245 nir_fixup_deref_modes(b->shader); 246 } 247 } else { 248 if (clipvertex) 249 cv = find_output(b->shader, clipvertex->data.driver_location); 250 else { 251 assert(position); 252 cv = find_output(b->shader, position->data.driver_location); 253 } 254 } 255 256 for (int plane = 0; plane < MAX_CLIP_PLANES; plane++) { 257 if (ucp_enables & (1 << plane)) { 258 nir_ssa_def *ucp = get_ucp(b, plane, clipplane_state_tokens); 259 260 /* calculate clipdist[plane] - dot(ucp, cv): */ 261 clipdist[plane] = nir_fdot(b, ucp, cv); 262 } else { 263 /* 0.0 == don't-clip == disabled: */ 264 clipdist[plane] = nir_imm_float(b, 0.0); 265 } 266 if (use_clipdist_array && plane < util_last_bit(ucp_enables)) { 267 assert(use_vars); 268 nir_deref_instr *deref; 269 deref = nir_build_deref_array_imm(b, 270 nir_build_deref_var(b, out[0]), 271 plane); 272 nir_store_deref(b, deref, clipdist[plane], 1); 273 } 274 } 275 276 if (!use_clipdist_array) { 277 if (use_vars) { 278 if (ucp_enables & 0x0f) 279 nir_store_var(b, out[0], nir_vec(b, clipdist, 4), 0xf); 280 if (ucp_enables & 0xf0) 281 nir_store_var(b, out[1], nir_vec(b, &clipdist[4], 4), 0xf); 282 } else { 283 if (ucp_enables & 0x0f) 284 store_clipdist_output(b, out[0], &clipdist[0]); 285 if (ucp_enables & 0xf0) 286 store_clipdist_output(b, out[1], &clipdist[4]); 287 } 288 } 289 } 290 291 /* 292 * VS lowering 293 */ 294 295 /* ucp_enables is bitmask of enabled ucps. Actual ucp values are 296 * passed in to shader via user_clip_plane system-values 297 * 298 * If use_vars is true, the pass will use variable loads and stores instead 299 * of working with store_output intrinsics. 300 * 301 * If use_clipdist_array is true, the pass will use compact arrays for the 302 * clipdist output instead of two vec4s. 303 */ 304 bool 305 nir_lower_clip_vs(nir_shader *shader, unsigned ucp_enables, bool use_vars, 306 bool use_clipdist_array, 307 const gl_state_index16 clipplane_state_tokens[][STATE_LENGTH]) 308 { 309 nir_function_impl *impl = nir_shader_get_entrypoint(shader); 310 nir_builder b; 311 nir_variable *position = NULL; 312 nir_variable *clipvertex = NULL; 313 nir_variable *out[2] = { NULL }; 314 315 if (!ucp_enables) 316 return false; 317 318 nir_builder_init(&b, impl); 319 320 /* NIR should ensure that, even in case of loops/if-else, there 321 * should be only a single predecessor block to end_block, which 322 * makes the perfect place to insert the clipdist calculations. 323 * 324 * NOTE: in case of early returns, these would have to be lowered 325 * to jumps to end_block predecessor in a previous pass. Not sure 326 * if there is a good way to sanity check this, but for now the 327 * users of this pass don't support sub-routines. 328 */ 329 assert(impl->end_block->predecessors->entries == 1); 330 b.cursor = nir_after_cf_list(&impl->body); 331 332 /* find clipvertex/position outputs */ 333 if (!find_clipvertex_and_position_outputs(shader, &clipvertex, &position)) 334 return false; 335 336 /* insert CLIPDIST outputs */ 337 create_clipdist_vars(shader, out, ucp_enables, true, 338 use_clipdist_array); 339 340 lower_clip_outputs(&b, position, clipvertex, out, ucp_enables, use_vars, 341 use_clipdist_array, clipplane_state_tokens); 342 343 nir_metadata_preserve(impl, nir_metadata_dominance); 344 345 return true; 346 } 347 348 static void 349 lower_clip_in_gs_block(nir_builder *b, nir_block *block, nir_variable *position, 350 nir_variable *clipvertex, nir_variable **out, 351 unsigned ucp_enables, bool use_clipdist_array, 352 const gl_state_index16 clipplane_state_tokens[][STATE_LENGTH]) 353 { 354 nir_foreach_instr_safe(instr, block) { 355 if (instr->type != nir_instr_type_intrinsic) 356 continue; 357 358 nir_intrinsic_instr *intrin = nir_instr_as_intrinsic(instr); 359 switch (intrin->intrinsic) { 360 case nir_intrinsic_emit_vertex_with_counter: 361 case nir_intrinsic_emit_vertex: 362 b->cursor = nir_before_instr(instr); 363 lower_clip_outputs(b, position, clipvertex, out, ucp_enables, true, 364 use_clipdist_array, clipplane_state_tokens); 365 break; 366 default: 367 /* not interesting; skip this */ 368 break; 369 } 370 } 371 } 372 373 /* 374 * GS lowering 375 */ 376 377 bool 378 nir_lower_clip_gs(nir_shader *shader, unsigned ucp_enables, 379 bool use_clipdist_array, 380 const gl_state_index16 clipplane_state_tokens[][STATE_LENGTH]) 381 { 382 nir_function_impl *impl = nir_shader_get_entrypoint(shader); 383 nir_builder b; 384 nir_variable *position = NULL; 385 nir_variable *clipvertex = NULL; 386 nir_variable *out[2] = { NULL }; 387 388 if (!ucp_enables) 389 return false; 390 391 /* find clipvertex/position outputs */ 392 if (!find_clipvertex_and_position_outputs(shader, &clipvertex, &position)) 393 return false; 394 395 /* insert CLIPDIST outputs */ 396 create_clipdist_vars(shader, out, ucp_enables, true, 397 use_clipdist_array); 398 399 nir_builder_init(&b, impl); 400 401 nir_foreach_block(block, impl) 402 lower_clip_in_gs_block(&b, block, position, clipvertex, out, 403 ucp_enables, use_clipdist_array, 404 clipplane_state_tokens); 405 406 nir_metadata_preserve(impl, nir_metadata_dominance); 407 408 return true; 409 } 410 411 /* 412 * FS lowering 413 */ 414 415 static void 416 lower_clip_fs(nir_function_impl *impl, unsigned ucp_enables, 417 nir_variable **in, bool use_clipdist_array) 418 { 419 nir_ssa_def *clipdist[MAX_CLIP_PLANES]; 420 nir_builder b; 421 422 nir_builder_init(&b, impl); 423 b.cursor = nir_before_cf_list(&impl->body); 424 425 if (!use_clipdist_array) { 426 if (ucp_enables & 0x0f) 427 load_clipdist_input(&b, in[0], 0, &clipdist[0]); 428 if (ucp_enables & 0xf0) 429 load_clipdist_input(&b, in[1], 0, &clipdist[4]); 430 } else { 431 if (ucp_enables & 0x0f) 432 load_clipdist_input(&b, in[0], 0, &clipdist[0]); 433 if (ucp_enables & 0xf0) 434 load_clipdist_input(&b, in[0], 1, &clipdist[4]); 435 } 436 437 for (int plane = 0; plane < MAX_CLIP_PLANES; plane++) { 438 if (ucp_enables & (1 << plane)) { 439 nir_ssa_def *cond; 440 441 cond = nir_flt(&b, clipdist[plane], nir_imm_float(&b, 0.0)); 442 nir_discard_if(&b, cond); 443 444 b.shader->info.fs.uses_discard = true; 445 } 446 } 447 448 nir_metadata_preserve(impl, nir_metadata_dominance); 449 } 450 451 static bool 452 fs_has_clip_dist_input_var(nir_shader *shader, nir_variable **io_vars, 453 unsigned *ucp_enables) 454 { 455 assert(shader->info.stage == MESA_SHADER_FRAGMENT); 456 nir_foreach_shader_in_variable(var, shader) { 457 switch (var->data.location) { 458 case VARYING_SLOT_CLIP_DIST0: 459 assert(var->data.compact); 460 io_vars[0] = var; 461 *ucp_enables &= (1 << glsl_get_length(var->type)) - 1; 462 return true; 463 default: 464 break; 465 } 466 } 467 return false; 468 } 469 470 /* insert conditional kill based on interpolated CLIPDIST 471 */ 472 bool 473 nir_lower_clip_fs(nir_shader *shader, unsigned ucp_enables, 474 bool use_clipdist_array) 475 { 476 nir_variable *in[2] = {0}; 477 478 if (!ucp_enables) 479 return false; 480 481 /* Fragment shaders can't read gl_ClipDistance[] in OpenGL so it will not 482 * have the variable defined, but Vulkan allows this, in which case the 483 * SPIR-V compiler would have already added it as a compact array. 484 */ 485 if (!fs_has_clip_dist_input_var(shader, in, &ucp_enables)) 486 create_clipdist_vars(shader, in, ucp_enables, false, use_clipdist_array); 487 else 488 assert(use_clipdist_array); 489 490 nir_foreach_function(function, shader) { 491 if (!strcmp(function->name, "main")) 492 lower_clip_fs(function->impl, ucp_enables, in, use_clipdist_array); 493 } 494 495 return true; 496 } 497