panfrost: add nir pass to lower noperspective varyings

Mali only supports perspective-correct varying interpolation in
hardware, so we have to emulate noperspective with lowering in both the
VS and FS.

Both vulkan and opengl allow mismatched interpolation qualifiers between
stages. Because we need all varyings that are noperspective in the FS to
be lowered in the VS, we cannot rely on the interpolation qualifiers in
the VS. Loading the set of noperspective varyings as a sysval allows the
implementation to pass them as a compile-time constant when known
statically, or a runtime push constant when not. Passing noperspective
varyings dynamically has a performance cost with unnecessary branches
and fmuls.

This sysval is not hooked up yet in either panfrost or panvk, so shader
compilation will fail.

Signed-off-by: Benjamin Lee <benjamin.lee@collabora.com>
Reviewed-by: Boris Brezillon <boris.brezillon@collabora.com>
Part-of: <https://gitlab.freedesktop.org/mesa/mesa/-/merge_requests/32127>
This commit is contained in:
Benjamin Lee 2024-10-28 11:34:53 -07:00 committed by Marge Bot
parent 6f541e2016
commit 081438ad39
5 changed files with 305 additions and 0 deletions

View file

@ -1527,6 +1527,11 @@ system_value("sample_positions_pan", 1, bit_sizes=[64])
# In a fragment shader, is the framebuffer single-sampled? 0/~0 bool
system_value("multisampled_pan", 1, bit_sizes=[32])
# In a vertex shader, a bitfield of varying slots that use noperspective
# interpolation in the linked fragment shader. Since special slots cannot be
# noperspective, this is 32 bits and starts from VARYING_SLOT_VAR0.
system_value("noperspective_varyings_pan", 1, bit_sizes=[32])
# R600 specific instrincs
#
# location where the tesselation data is stored in LDS

View file

@ -5268,6 +5268,11 @@ bifrost_preprocess_nir(nir_shader *nir, unsigned gpu_id)
NIR_PASS(_, nir, nir_lower_io, nir_var_shader_in | nir_var_shader_out,
glsl_type_size, nir_lower_io_use_interpolated_input_intrinsics);
if (nir->info.stage == MESA_SHADER_VERTEX)
NIR_PASS_V(nir, pan_nir_lower_noperspective_vs);
if (nir->info.stage == MESA_SHADER_FRAGMENT)
NIR_PASS_V(nir, pan_nir_lower_noperspective_fs);
/* nir_lower[_explicit]_io is lazy and emits mul+add chains even for
* offsets it could figure out are constant. Do some constant folding
* before bifrost_nir_lower_store_component below.

View file

@ -14,6 +14,7 @@ libpanfrost_util_files = files(
'pan_lower_helper_invocation.c',
'pan_lower_image_index.c',
'pan_lower_image_ms.c',
'pan_lower_noperspective.c',
'pan_lower_sample_position.c',
'pan_lower_store_component.c',
'pan_lower_writeout.c',

View file

@ -387,6 +387,8 @@ bool pan_nir_lower_store_component(nir_shader *shader);
bool pan_nir_lower_image_ms(nir_shader *shader);
bool pan_nir_lower_frag_coord_zw(nir_shader *shader);
bool pan_nir_lower_noperspective_vs(nir_shader *shader);
bool pan_nir_lower_noperspective_fs(nir_shader *shader);
bool pan_lower_helper_invocation(nir_shader *shader);
bool pan_lower_sample_pos(nir_shader *shader);

View file

@ -0,0 +1,292 @@
/*
* Copyright © 2024 Collabora Ltd.
* SPDX-License-Identifier: MIT
*/
#include "compiler/nir/nir_builder.h"
#include "pan_ir.h"
/* Mali only provides instructions to fetch varyings with either flat or
* perspective-correct interpolation. This pass lowers noperspective varyings
* to perspective-correct varyings by multiplying by W in the VS and dividing
* by W in the FS.
*
* This pass needs to lower noperspective varyings in the VS, however Vulkan
* and OpenGL do not require interpolation qualifiers to match between stages.
* Only the qualifiers in the fragment shader matter. To handle this, we load
* a bitfield of noperspective varyings in the linked FS from the
* 'noperspective_varyings_pan' sysval in the VS. If the FS qualifiers are
* known at compile-time (for example, with monolithic pipelines in vulkan),
* this may be lowered to a constant.
*
* This pass is expected to run after nir_lower_io_to_temporaries and
* nir_lower_io, so each IO location must have at most one read or write.
* These properties are preserved.
*
* This pass is expected to run after nir_lower_viewport_transform, so
* gl_Position.w is actually 1 / gl_Position.w. This is because
* nir_lower_viewport_transform may clamp large W values, and we need to use
* the clamped value here. */
static nir_intrinsic_instr *
find_pos_store(nir_function_impl *impl)
{
/* nir_lower_io_to_temporaries ensures all stores are in the exit block */
nir_block *block = nir_impl_last_block(impl);
nir_foreach_instr_safe(instr, block) {
if (instr->type != nir_instr_type_intrinsic)
continue;
nir_intrinsic_instr *intrin = nir_instr_as_intrinsic(instr);
if (intrin->intrinsic != nir_intrinsic_store_output)
continue;
nir_io_semantics sem = nir_intrinsic_io_semantics(intrin);
if (sem.location == VARYING_SLOT_POS)
return intrin;
}
return NULL;
}
static bool
is_noperspective_load(nir_intrinsic_instr* intrin)
{
if (intrin->intrinsic != nir_intrinsic_load_interpolated_input)
return false;
nir_intrinsic_instr *bary_instr = nir_src_as_intrinsic(intrin->src[0]);
assert(bary_instr);
return nir_intrinsic_interp_mode(bary_instr) == INTERP_MODE_NOPERSPECTIVE;
}
static bool
has_noperspective_load(nir_function_impl *impl)
{
/* nir_lower_io_to_temporaries ersures all loads are in the first block */
nir_block *block = nir_start_block(impl);
nir_foreach_instr(instr, block) {
if (instr->type != nir_instr_type_intrinsic)
continue;
nir_intrinsic_instr *intrin = nir_instr_as_intrinsic(instr);
if (is_noperspective_load(intrin))
return true;
}
return false;
}
/**
* Returns a bitfield of VS outputs where it is known at compile-time that
* noperspective interpolation may be used at runtime. Similar to the
* noperspective_varyings_pan sysval, this bitfield only covers user varyings
* (starting at VARYING_SLOT_VAR0).
*
* Precomputed because struct outputs may be split into multiple store_output
* intrinsics. If any struct members are integers, then the whole struct
* cannot be noperspective.
*/
static uint32_t
get_maybe_noperspective_outputs(nir_function_impl *impl)
{
uint32_t used_outputs = 0;
uint32_t integer_outputs = 0;
/* nir_lower_io_to_temporaries ensures all stores are in the exit block */
nir_block *block = nir_impl_last_block(impl);
nir_foreach_instr_safe(instr, block) {
if (instr->type != nir_instr_type_intrinsic)
continue;
nir_intrinsic_instr *intrin = nir_instr_as_intrinsic(instr);
if (intrin->intrinsic != nir_intrinsic_store_output)
continue;
nir_io_semantics sem = nir_intrinsic_io_semantics(intrin);
if (sem.location < VARYING_SLOT_VAR0)
continue;
uint32_t location_bit = BITFIELD_BIT(sem.location - VARYING_SLOT_VAR0);
used_outputs |= location_bit;
nir_alu_type type = nir_intrinsic_src_type(intrin);
nir_alu_type base_type = nir_alu_type_get_base_type(type);
if (base_type == nir_type_int ||
base_type == nir_type_uint ||
base_type == nir_type_bool)
integer_outputs |= location_bit;
}
/* From the Vulkan 1.1.301 spec:
*
* "Output attributes of integer or unsigned integer type must always be
* flat shaded."
*
* From the OpenGL 4.6 spec:
*
* "Implementations need not support interpolation of output values of
* integer or unsigned integer type, as all such attributes must be flat
* shaded."
*
* So we can assume varyings that contain integers are never noperspective.
*/
return used_outputs & ~integer_outputs;
}
static bool
is_maybe_noperspective_output(unsigned location,
uint32_t maybe_noperspective_outputs)
{
return location >= VARYING_SLOT_VAR0 &&
maybe_noperspective_outputs & BITFIELD_BIT(location - VARYING_SLOT_VAR0);
}
static nir_def *
is_noperspective_output(nir_builder *b, unsigned location,
nir_def *noperspective_outputs)
{
if (location < VARYING_SLOT_VAR0)
return nir_imm_bool(b, false);
uint32_t bit = BITFIELD_BIT(location - VARYING_SLOT_VAR0);
return nir_i2b(b, nir_iand_imm(b, noperspective_outputs, bit));
}
struct lower_noperspective_vs_state {
nir_def *pos_w;
uint32_t maybe_noperspective_outputs;
nir_def *noperspective_outputs;
};
/**
* Multiply all noperspective varying stores by gl_Position.w
*/
static bool
lower_noperspective_vs(nir_builder *b, nir_intrinsic_instr *intrin,
void *data)
{
struct lower_noperspective_vs_state *state = data;
if (intrin->intrinsic != nir_intrinsic_store_output)
return false;
nir_io_semantics sem = nir_intrinsic_io_semantics(intrin);
if (!is_maybe_noperspective_output(sem.location,
state->maybe_noperspective_outputs))
return false;
b->cursor = nir_before_instr(&intrin->instr);
nir_def *is_noperspective =
is_noperspective_output(b, sem.location, state->noperspective_outputs);
nir_def *old_value = intrin->src[0].ssa;
nir_def *noperspective_value = nir_fmul(b, old_value, state->pos_w);
nir_def *new_value =
nir_bcsel(b, is_noperspective, noperspective_value, old_value);
nir_src_rewrite(&intrin->src[0], new_value);
return true;
}
/**
* Multiply all noperspective varying loads by gl_FragCoord.w
*/
static bool
lower_noperspective_fs(nir_builder *b, nir_intrinsic_instr *intrin,
void *data)
{
if (!is_noperspective_load(intrin))
return false;
b->cursor = nir_after_instr(&intrin->instr);
nir_def *bary = intrin->src[0].ssa;
nir_def *fragcoord_w = nir_load_frag_coord_zw_pan(b, bary, .component = 3);
nir_def *new_value = nir_fmul(b, &intrin->def, fragcoord_w);
nir_def_rewrite_uses_after(&intrin->def, new_value, new_value->parent_instr);
return true;
}
/**
* Move all stores to output variables that occur before the specified
* instruction in the same block to after the specified instruction.
*/
static void
move_output_stores_after(nir_instr *after)
{
nir_cursor cursor = nir_after_instr(after);
nir_block *block = nir_cursor_current_block(cursor);
nir_foreach_instr_safe(instr, block) {
if (instr == after)
break;
if (instr->type != nir_instr_type_intrinsic)
continue;
nir_intrinsic_instr *intrin = nir_instr_as_intrinsic(instr);
if (intrin->intrinsic == nir_intrinsic_store_output)
nir_instr_move(cursor, instr);
}
}
bool
pan_nir_lower_noperspective_vs(nir_shader *shader)
{
assert(shader->info.stage == MESA_SHADER_VERTEX);
if (!(shader->info.outputs_written & VARYING_BIT_POS))
return false;
nir_function_impl *impl = nir_shader_get_entrypoint(shader);
uint32_t maybe_noperspective_outputs = get_maybe_noperspective_outputs(impl);
if (!maybe_noperspective_outputs)
return false;
nir_intrinsic_instr *pos_store = find_pos_store(impl);
assert(pos_store);
assert(nir_intrinsic_write_mask(pos_store) & BITFIELD_BIT(3));
nir_builder b = nir_builder_at(nir_after_instr(&pos_store->instr));
/* This is after nir_lower_viewport_transform, so stored W is 1/W */
nir_def *pos_w_recip = nir_channel(&b, pos_store->src[0].ssa, 3);
nir_def *pos_w = nir_frcp(&b, pos_w_recip);
/* Reorder stores to ensure pos_w def is available */
move_output_stores_after(pos_w->parent_instr);
nir_def *noperspective_outputs = nir_load_noperspective_varyings_pan(&b);
struct lower_noperspective_vs_state state = {
.pos_w = pos_w,
.maybe_noperspective_outputs = maybe_noperspective_outputs,
.noperspective_outputs = noperspective_outputs,
};
nir_shader_intrinsics_pass(shader, lower_noperspective_vs,
nir_metadata_control_flow |
nir_metadata_loop_analysis,
(void *)&state);
return true;
}
bool
pan_nir_lower_noperspective_fs(nir_shader *shader)
{
assert(shader->info.stage == MESA_SHADER_FRAGMENT);
nir_function_impl *impl = nir_shader_get_entrypoint(shader);
if (!has_noperspective_load(impl))
return false;
nir_shader_intrinsics_pass(shader, lower_noperspective_fs,
nir_metadata_control_flow, NULL);
return true;
}