brw: Rewrite legacy tess level remapping

This unifies the dynamic (SSO) and fixed (linked together) versions.
We emit piles of NIR as if we were doing the dynamic version, but
replace the tess config field access with constant values.  It all
should optimize away back to something reasonable.  We lower these
directly to URB read/write intrinsics.

It also rewrites the dynamic version to directly read/write the URB
rather than going through temporaries.  The old version was broken
in that tessellation control shader invocations can technically use
the shared output area for cross-invocation data sharing with barriers,
although doing so using the built-in tesslevel patch outputs is very
unlikely.

Reviewed-by: Alyssa Rosenzweig <alyssa.rosenzweig@intel.com>
Part-of: <https://gitlab.freedesktop.org/mesa/mesa/-/merge_requests/38482>
This commit is contained in:
Kenneth Graunke
2025-10-27 15:45:45 -07:00
committed by Marge Bot
parent ee407481c2
commit d95a9714c2
+106 -221
View File
@@ -270,242 +270,129 @@ lower_outputs_to_urb_intrinsics(nir_shader *nir,
}
static bool
remap_tess_levels_legacy_static(nir_builder *b,
nir_intrinsic_instr *intr,
void *data)
remap_tess_levels_legacy(nir_builder *b,
nir_intrinsic_instr *intrin,
void *data)
{
if (!(b->shader->info.stage == MESA_SHADER_TESS_CTRL && is_output(intr)) &&
!(b->shader->info.stage == MESA_SHADER_TESS_EVAL && is_input(intr)))
/* Note that this pass does not work with Xe2 LSC URB messages, but
* we never use legacy layouts there anyway.
*/
enum tess_primitive_mode prim = (uintptr_t) data;
if (!(b->shader->info.stage == MESA_SHADER_TESS_CTRL && is_output(intrin)) &&
!(b->shader->info.stage == MESA_SHADER_TESS_EVAL && is_input(intrin)))
return false;
/* Handled in a different pass */
nir_io_semantics io_sem = nir_intrinsic_io_semantics(intr);
const nir_io_semantics io_sem = nir_intrinsic_io_semantics(intrin);
if (io_sem.location != VARYING_SLOT_TESS_LEVEL_INNER &&
io_sem.location != VARYING_SLOT_TESS_LEVEL_OUTER)
return false;
const unsigned component = nir_intrinsic_component(intr);
bool out_of_bounds = false;
bool write = !nir_intrinsic_infos[intr->intrinsic].has_dest;
unsigned mask = write ? nir_intrinsic_write_mask(intr) : 0;
nir_def *src = NULL, *dest = NULL;
b->cursor = nir_before_instr(&intrin->instr);
enum tess_primitive_mode _primitive_mode = (uintptr_t)data;
if (write) {
assert(intr->num_components == intr->src[0].ssa->num_components);
} else {
assert(intr->num_components == intr->def.num_components);
}
if (io_sem.location == VARYING_SLOT_TESS_LEVEL_INNER) {
b->cursor = write ? nir_before_instr(&intr->instr)
: nir_after_instr(&intr->instr);
switch (_primitive_mode) {
case TESS_PRIMITIVE_QUADS:
/* gl_TessLevelInner[0..1] lives at DWords 3-2 (reversed). */
nir_intrinsic_set_base(intr, 0);
if (write) {
assert(intr->src[0].ssa->num_components == 2);
intr->num_components = 4;
nir_def *undef = nir_undef(b, 1, 32);
nir_def *x = nir_channel(b, intr->src[0].ssa, 0);
nir_def *y = nir_channel(b, intr->src[0].ssa, 1);
src = nir_vec4(b, undef, undef, y, x);
mask = !!(mask & WRITEMASK_X) << 3 | !!(mask & WRITEMASK_Y) << 2;
} else if (intr->def.num_components > 1) {
assert(intr->def.num_components == 2);
intr->num_components = 4;
intr->def.num_components = 4;
unsigned wz[2] = { 3, 2 };
dest = nir_swizzle(b, &intr->def, wz, 2);
} else {
nir_intrinsic_set_component(intr, 3 - component);
}
break;
case TESS_PRIMITIVE_TRIANGLES:
/* gl_TessLevelInner[0] lives at DWord 4. */
nir_intrinsic_set_base(intr, 1);
mask &= WRITEMASK_X;
out_of_bounds = component > 0;
break;
case TESS_PRIMITIVE_ISOLINES:
out_of_bounds = true;
break;
default:
UNREACHABLE("Bogus tessellation domain");
}
} else {
b->cursor = write ? nir_before_instr(&intr->instr)
: nir_after_instr(&intr->instr);
nir_intrinsic_set_base(intr, 1);
switch (_primitive_mode) {
case TESS_PRIMITIVE_QUADS:
case TESS_PRIMITIVE_TRIANGLES:
/* Quads: gl_TessLevelOuter[0..3] lives at DWords 7-4 (reversed).
* Triangles: gl_TessLevelOuter[0..2] lives at DWords 7-5 (reversed).
*/
if (write) {
assert(intr->src[0].ssa->num_components == 4);
unsigned wzyx[4] = { 3, 2, 1, 0 };
src = nir_swizzle(b, intr->src[0].ssa, wzyx, 4);
mask = !!(mask & WRITEMASK_X) << 3 | !!(mask & WRITEMASK_Y) << 2 |
!!(mask & WRITEMASK_Z) << 1 | !!(mask & WRITEMASK_W) << 0;
/* Don't overwrite the inner factor at DWord 4 for triangles */
if (_primitive_mode == TESS_PRIMITIVE_TRIANGLES)
mask &= ~WRITEMASK_X;
} else if (intr->def.num_components > 1) {
assert(intr->def.num_components == 4);
unsigned wzyx[4] = { 3, 2, 1, 0 };
dest = nir_swizzle(b, &intr->def, wzyx, 4);
} else {
nir_intrinsic_set_component(intr, 3 - component);
out_of_bounds = component == 3 &&
_primitive_mode == TESS_PRIMITIVE_TRIANGLES;
}
break;
case TESS_PRIMITIVE_ISOLINES:
/* gl_TessLevelOuter[0..1] lives at DWords 6-7 (in order). */
if (write) {
assert(intr->src[0].ssa->num_components == 4);
nir_def *undef = nir_undef(b, 1, 32);
nir_def *x = nir_channel(b, intr->src[0].ssa, 0);
nir_def *y = nir_channel(b, intr->src[0].ssa, 1);
src = nir_vec4(b, undef, undef, x, y);
mask = !!(mask & WRITEMASK_X) << 2 | !!(mask & WRITEMASK_Y) << 3;
} else {
nir_intrinsic_set_component(intr, 2 + component);
out_of_bounds = component > 1;
}
break;
default:
UNREACHABLE("Bogus tessellation domain");
}
}
if (out_of_bounds) {
if (!write)
nir_def_rewrite_uses(&intr->def, nir_undef(b, 1, 32));
nir_instr_remove(&intr->instr);
} else if (write) {
nir_intrinsic_set_write_mask(intr, mask);
if (src) {
nir_src_rewrite(&intr->src[0], src);
}
} else if (dest) {
nir_def_rewrite_uses_after(&intr->def, dest);
}
return true;
}
struct tess_levels_temporary_state {
nir_variable *inner_factors_var;
nir_variable *outer_factors_var;
};
static bool
remap_tess_levels_to_temporary(nir_builder *b, nir_intrinsic_instr *intrin, void *data)
{
if (!(b->shader->info.stage == MESA_SHADER_TESS_CTRL && is_output(intrin)))
return false;
/* Handled in a different pass */
nir_io_semantics io_sem = nir_intrinsic_io_semantics(intrin);
if (io_sem.location != VARYING_SLOT_TESS_LEVEL_INNER &&
io_sem.location != VARYING_SLOT_TESS_LEVEL_OUTER)
return false;
struct tess_levels_temporary_state *state = data;
nir_variable *var = io_sem.location == VARYING_SLOT_TESS_LEVEL_INNER ?
state->inner_factors_var : state->outer_factors_var;
if (nir_intrinsic_infos[intrin->intrinsic].has_dest) {
b->cursor = nir_after_instr(&intrin->instr);
nir_def_replace(&intrin->def, nir_load_var(b, var));
} else {
b->cursor = nir_instr_remove(&intrin->instr);
nir_store_var(b, var, nir_pad_vector(b, intrin->src[0].ssa, 4),
nir_intrinsic_write_mask(intrin));
}
return true;
}
static bool
remap_tess_levels_legacy_dynamic(nir_shader *nir,
const struct intel_device_info *devinfo)
{
nir_function_impl *impl = nir_shader_get_entrypoint(nir);
struct tess_levels_temporary_state state = {
.inner_factors_var = nir_local_variable_create(impl, glsl_vec4_type(),
"__temp_inner_factors"),
.outer_factors_var = nir_local_variable_create(impl, glsl_vec4_type(),
"__temp_outer_factors"),
};
nir_shader_intrinsics_pass(nir, remap_tess_levels_to_temporary,
nir_metadata_control_flow, &state);
nir_builder _b = nir_builder_at(nir_after_impl(impl)), *b = &_b;
const bool inner = io_sem.location == VARYING_SLOT_TESS_LEVEL_INNER;
nir_def *tess_config = nir_load_tess_config_intel(b);
nir_def *is_quad =
nir_test_mask(b, tess_config, INTEL_TESS_CONFIG_QUADS);
nir_def *is_tri =
nir_test_mask(b, tess_config, INTEL_TESS_CONFIG_TRIANGLES);
nir_def *is_quad_tri =
nir_test_mask(b, tess_config, (INTEL_TESS_CONFIG_QUADS |
INTEL_TESS_CONFIG_TRIANGLES));
nir_def *zero = nir_imm_int(b, 0);
prim == TESS_PRIMITIVE_UNSPECIFIED ?
nir_test_mask(b, tess_config, INTEL_TESS_CONFIG_TRIANGLES) :
nir_imm_bool(b, prim == TESS_PRIMITIVE_TRIANGLES);
nir_def *is_isoline =
prim == TESS_PRIMITIVE_UNSPECIFIED ?
nir_test_mask(b, tess_config, INTEL_TESS_CONFIG_ISOLINES) :
nir_imm_bool(b, prim == TESS_PRIMITIVE_ISOLINES);
/* Format below is described in the SKL PRMs, Volume 7: 3D-Media-GPGPU,
* Patch URB Entry (Patch Record) Output, Patch Header DW0-7
/* The patch layout is described in the SKL PRMs, Volume 7: 3D-Media-GPGPU,
* Patch URB Entry (Patch Record) Output, Patch Header DW0-7. In the chart
* below TessLevelInner = <ix, iy> and TessLevelOuter = <x, y, z, w>:
*
* Based on topology we use one of those :
* - Patch Header: QUAD Domain / LEGACY Patch Header Layout
* - Patch Header: TRI Domain / LEGACY Patch Header Layout
* - Patch Header: ISOLINE Domain / LEGACY Patch Header Layout
* [ 7 6 5 4 | 3 2 1 0]
*
* There are more convenient layouts in more recent generations but they're
* not available on all platforms.
* [ x y z w | ix iy __ __] quad legacy
* [ x y z ix | __ __ __ __] tri legacy
* [ y x __ __ | __ __ __ __] isoline legacy
*
* From this, we can see:
* - Outer lives at slot 1
* - Inner lives at slot 0 for quads but slot 1 for triangles
* - Inner does not exist for isolines
* - Isolines need the original value but mask << 2
* - Triangles+Inner need the original value and mask
* - Quads or Triangles+Outer need the value and mask flipped (WYZX)
*/
nir_def *values[8] = {
zero,
zero,
nir_bcsel(b, is_quad_tri, nir_load_array_var_imm(b, state.inner_factors_var, 1), zero),
nir_bcsel(b, is_quad_tri, nir_load_array_var_imm(b, state.inner_factors_var, 0), zero),
if (intrin->intrinsic == nir_intrinsic_load_input) {
/* The TES is guaranteed to know the primitive mode and we always
* push the first two input slots.
*/
assert(b->shader->info.stage == MESA_SHADER_TESS_EVAL);
assert(prim != TESS_PRIMITIVE_UNSPECIFIED);
nir_bcsel(b, is_quad, nir_load_array_var_imm(b, state.outer_factors_var, 3),
nir_bcsel(b, is_tri, nir_load_array_var_imm(b, state.inner_factors_var, 0),
zero)),
nir_bcsel(b, is_quad_tri, nir_load_array_var_imm(b, state.outer_factors_var, 2), zero),
nir_bcsel(b, is_quad_tri, nir_load_array_var_imm(b, state.outer_factors_var, 1),
nir_load_array_var_imm(b, state.outer_factors_var, 0)),
nir_bcsel(b, is_quad_tri, nir_load_array_var_imm(b, state.outer_factors_var, 0),
nir_load_array_var_imm(b, state.outer_factors_var, 1)),
};
nir_def *result;
if (inner && prim == TESS_PRIMITIVE_TRIANGLES) {
result = load_push_input(b, intrin, 4 * sizeof(uint32_t));
} else if (prim == TESS_PRIMITIVE_ISOLINES) {
result = load_push_input(b, intrin, 6 * sizeof(uint32_t));
} else {
const unsigned start =
(inner ? 4 : 8) - nir_intrinsic_component(intrin)
- intrin->def.num_components;
nir_store_output(b, nir_vec(b, &values[0], 4), zero, .base = 0,
.io_semantics.location = VARYING_SLOT_TESS_LEVEL_INNER);
nir_store_output(b, nir_vec(b, &values[4], 4), zero, .base = 1,
.io_semantics.location = VARYING_SLOT_TESS_LEVEL_OUTER);
nir_def *tmp = load_push_input(b, intrin, start * sizeof(uint32_t));
nir_progress(true, impl, nir_metadata_none);
unsigned reverse[NIR_MAX_VEC_COMPONENTS];
for (unsigned i = 0; i < tmp->num_components; ++i)
reverse[i] = tmp->num_components - 1 - i;
result = nir_swizzle(b, tmp, reverse, tmp->num_components);
}
nir_def_replace(&intrin->def, result);
} else {
assert(b->shader->info.stage == MESA_SHADER_TESS_CTRL);
const unsigned wzyx[4] = { 3, 2, 1, 0 };
const unsigned xxxy[4] = { 0, 0, 0, 1 };
const unsigned zwww[4] = { 2, 3, 3, 3 };
nir_def *slot = inner ? nir_b2i32(b, is_tri) : nir_imm_int(b, 1);
if (intrin->intrinsic == nir_intrinsic_store_output) {
const unsigned mask = nir_intrinsic_write_mask(intrin);
const unsigned revmask =
!!(mask & WRITEMASK_X) << 3 | !!(mask & WRITEMASK_Y) << 2 |
!!(mask & WRITEMASK_Z) << 1 | !!(mask & WRITEMASK_W) << 0;
nir_def *padded = nir_pad_vector_imm_int(b, intrin->src[0].ssa, 0, 4);
nir_def *new_val =
inner ? nir_bcsel(b, is_tri, nir_channel(b, padded, 0),
nir_swizzle(b, padded, wzyx, 4))
: nir_bcsel(b, is_isoline, nir_swizzle(b, padded, xxxy, 4),
nir_swizzle(b, padded, wzyx, 4));
nir_def *new_mask =
inner ? nir_bcsel(b, is_tri, nir_imm_int(b, mask & WRITEMASK_X),
nir_imm_int(b, revmask))
: nir_bcsel(b, is_isoline, nir_imm_int(b, mask << 2),
nir_bcsel(b, is_tri, nir_imm_int(b, revmask & WRITEMASK_YZW),
nir_imm_int(b, revmask)));
nir_store_urb_vec4_intel(b, new_val, output_handle(b),
slot, new_mask);
nir_instr_remove(&intrin->instr);
} else {
assert(intrin->intrinsic == nir_intrinsic_load_output);
nir_def *vec =
nir_load_urb_vec4_intel(b, 4, 32, output_handle(b), slot);
const unsigned nc = intrin->def.num_components;
nir_def *result =
inner ? nir_bcsel(b, is_tri, nir_trim_vector(b, vec, nc),
nir_swizzle(b, vec, wzyx, nc))
: nir_bcsel(b, is_isoline, nir_swizzle(b, vec, zwww, nc),
nir_swizzle(b, vec, wzyx, nc));
nir_def_replace(&intrin->def, result);
}
}
return true;
}
@@ -594,9 +481,7 @@ remap_tess_levels(nir_shader *nir,
{
/* Pre-Gfx12 use legacy patch header layouts */
if (devinfo->ver < 12) {
return prim == TESS_PRIMITIVE_UNSPECIFIED ?
remap_tess_levels_legacy_dynamic(nir, devinfo) :
nir_shader_intrinsics_pass(nir, remap_tess_levels_legacy_static,
return nir_shader_intrinsics_pass(nir, remap_tess_levels_legacy,
nir_metadata_control_flow,
(void *)(uintptr_t) prim);
}