Files
intel-hard-graphics/src/imagination/pco/pco_nir_pvfio.c
T
2026-06-06 13:25:15 +03:00

1474 lines
47 KiB
C

/*
* Copyright © 2024 Imagination Technologies Ltd.
*
* SPDX-License-Identifier: MIT
*/
/**
* \file pco_nir_pvfio.c
*
* \brief PCO NIR per-vertex/fragment input/output passes.
*/
#include "compiler/glsl_types.h"
#include "compiler/shader_enums.h"
#include "nir.h"
#include "nir_builder.h"
#include "nir_format_convert.h"
#include "pco.h"
#include "pco_builder.h"
#include "pco_internal.h"
#include "util/macros.h"
#include "util/u_dynarray.h"
#include <assert.h>
#include <stdbool.h>
#include <stdio.h>
/** Per-fragment output pass state. */
struct pfo_state {
struct util_dynarray loads; /** List of fragment loads. */
struct util_dynarray stores; /** List of fragment stores. */
/* Src for depth feedback (NULL if unused). */
nir_def *depth_feedback_src;
nir_def *discard_cond_reg;
bool has_discards;
nir_intrinsic_instr *last_discard_store;
bool has_sample_check;
/* nir_instr *terminate; */
pco_fs_data *fs; /** Fragment-specific data. */
};
/** Per-vertex input pass state. */
struct pvi_state {
nir_def *attribs[MAX_VERTEX_GENERIC_ATTRIBS]; /** Loaded vertex attribs. */
pco_vs_data *vs; /** Vertex-specific data. */
};
/**
* \brief Returns the GLSL base type equivalent of a pipe format.
*
* \param[in] format Pipe format.
* \return The GLSL base type, or GLSL_TYPE_ERROR if unsupported/invalid.
*/
static inline enum glsl_base_type base_type_from_fmt(enum pipe_format format)
{
const struct util_format_description *desc = util_format_description(format);
int chan = util_format_get_first_non_void_channel(format);
if (chan < 0)
return GLSL_TYPE_ERROR;
switch (desc->channel[chan].type) {
case UTIL_FORMAT_TYPE_UNSIGNED:
return GLSL_TYPE_UINT;
case UTIL_FORMAT_TYPE_SIGNED:
return GLSL_TYPE_INT;
case UTIL_FORMAT_TYPE_FLOAT:
return GLSL_TYPE_FLOAT;
default:
break;
}
return GLSL_TYPE_ERROR;
}
static enum pipe_format
to_pbe_format(nir_builder *b, enum pipe_format format, nir_def **input)
{
switch (format) {
case PIPE_FORMAT_B5G6R5_UNORM:
return PIPE_FORMAT_R8G8B8_UNORM;
case PIPE_FORMAT_A4R4G4B4_UNORM:
case PIPE_FORMAT_B5G5R5A1_UNORM:
case PIPE_FORMAT_B8G8R8A8_UNORM:
return PIPE_FORMAT_R8G8B8A8_UNORM;
case PIPE_FORMAT_R8G8B8A8_SRGB:
case PIPE_FORMAT_B8G8R8A8_SRGB:
if (input)
*input = nir_fsat(b, *input);
FALLTHROUGH;
case PIPE_FORMAT_R10G10B10A2_UNORM:
return PIPE_FORMAT_R16G16B16A16_FLOAT;
case PIPE_FORMAT_R11G11B10_FLOAT:
return PIPE_FORMAT_R16G16B16_FLOAT;
/* For loadops. */
case PIPE_FORMAT_Z32_FLOAT:
case PIPE_FORMAT_Z32_FLOAT_S8X24_UINT:
case PIPE_FORMAT_Z24_UNORM_S8_UINT:
case PIPE_FORMAT_Z16_UNORM:
case PIPE_FORMAT_Z24X8_UNORM:
assert(b->shader->info.internal);
return PIPE_FORMAT_R32_FLOAT;
default:
break;
}
return format;
}
static unsigned format_chans_per_dword(enum pipe_format format)
{
const struct util_format_description *desc = util_format_description(format);
unsigned accum_bits = 0;
for (unsigned u = 0; u < desc->nr_channels; ++u) {
/* Exactly one dword, great! */
if (accum_bits == 32)
return u;
/* Went over, back off by one. */
if (accum_bits > 32) {
/* We don't support formats with channels > 1 dword. */
assert(u > 1);
return u - 1;
}
accum_bits += desc->channel[u].size;
}
/* Loop finished, all channels can fit. */
return desc->nr_channels;
}
static nir_def *pack_to_format(nir_builder *b,
nir_def *input,
nir_alu_type src_type,
enum pipe_format format)
{
const struct util_format_description *desc = util_format_description(format);
nir_def *zero = nir_imm_int(b, 0);
nir_def *one = nir_alu_type_get_base_type(src_type) == nir_type_float
? nir_imm_float(b, 1.0f)
: nir_imm_int(b, 1);
nir_def *input_comps[4];
/* Populate any missing components .*/
for (unsigned u = 0; u < ARRAY_SIZE(input_comps); ++u) {
enum pipe_swizzle s = desc->swizzle[u];
if (s <= PIPE_SWIZZLE_W)
input_comps[u] = nir_channel(b, input, s);
else if (s == PIPE_SWIZZLE_0)
input_comps[u] = zero;
else if (s == PIPE_SWIZZLE_1)
input_comps[u] = one;
else
UNREACHABLE("");
}
unsigned format_bits = util_format_get_blocksizebits(format);
unsigned format_dwords = DIV_ROUND_UP(format_bits, 32);
nir_def *packed_comps[] = { zero, zero, zero, zero };
/* Special case: no packing required. */
if (util_format_get_max_channel_size(format) == 32)
return nir_vec(b, input_comps, format_dwords);
/* Special case: can't be packed with op, need bit-packing instead. */
if (util_format_is_pure_integer(format)) {
for (unsigned u = 0; u < desc->nr_channels; ++u) {
unsigned dword = desc->channel[u].shift / 32;
unsigned offset = desc->channel[u].shift % 32;
unsigned size = desc->channel[u].size;
packed_comps[dword] = nir_bitfield_insert_imm(b,
packed_comps[dword],
input_comps[u],
offset,
size);
}
return nir_vec(b, packed_comps, format_dwords);
}
unsigned chans_per_dword = format_chans_per_dword(format);
unsigned chans_remaining = desc->nr_channels;
input = nir_vec(b, input_comps, desc->nr_channels);
for (unsigned u = 0; u < format_dwords; ++u) {
unsigned chans_to_pack =
chans_remaining > chans_per_dword ? chans_per_dword : chans_remaining;
unsigned chans_packed = desc->nr_channels - chans_remaining;
nir_def *input_chans =
nir_channels(b, input, BITFIELD_RANGE(chans_packed, chans_to_pack));
packed_comps[u] = nir_pack_pco(b, input_chans, .format = format);
chans_remaining -= chans_to_pack;
}
assert(!chans_remaining);
return nir_vec(b, packed_comps, format_dwords);
}
static nir_def *unpack_from_format(nir_builder *b,
nir_def *packed_comps[static 4],
nir_alu_type dest_type,
enum pipe_format format,
unsigned components_needed)
{
const struct util_format_description *desc = util_format_description(format);
nir_def *unpacked_comps[4];
unsigned format_bits = util_format_get_blocksizebits(format);
unsigned format_dwords = DIV_ROUND_UP(format_bits, 32);
/* Special case: no unpacking required. */
if (util_format_get_max_channel_size(format) == 32) {
for (unsigned u = 0; u < desc->nr_channels; ++u)
unpacked_comps[u] = packed_comps[u];
}
/* Special case: can't be unpacked with op, need bit-unpacking instead. */
else if (util_format_is_pure_integer(format)) {
nir_def *(*nir_bitfield_extract_imm)(nir_builder *,
nir_def *,
uint32_t,
uint32_t) =
util_format_is_pure_uint(format) ? nir_ubitfield_extract_imm
: nir_ibitfield_extract_imm;
for (unsigned u = 0; u < desc->nr_channels; ++u) {
unsigned dword = desc->channel[u].shift / 32;
unsigned offset = desc->channel[u].shift % 32;
unsigned size = desc->channel[u].size;
unpacked_comps[u] =
nir_bitfield_extract_imm(b, packed_comps[dword], offset, size);
}
}
else {
unsigned chans_per_dword = format_chans_per_dword(format);
unsigned chans_remaining = desc->nr_channels;
for (unsigned u = 0; u < format_dwords; ++u) {
unsigned chans_to_unpack = chans_remaining > chans_per_dword
? chans_per_dword
: chans_remaining;
nir_def *unpacked = nir_unpack_pco(b,
chans_to_unpack,
packed_comps[u],
.format = format);
unsigned chans_unpacked = desc->nr_channels - chans_remaining;
for (unsigned v = 0; v < chans_to_unpack; ++v)
unpacked_comps[chans_unpacked + v] = nir_channel(b, unpacked, v);
chans_remaining -= chans_to_unpack;
}
assert(!chans_remaining);
}
nir_def *zero = nir_imm_int(b, 0);
nir_def *one = nir_alu_type_get_base_type(dest_type) == nir_type_float
? nir_imm_float(b, 1.0f)
: nir_imm_int(b, 1);
nir_def *output_comps[4];
/* Populate any missing components .*/
for (unsigned u = 0; u < ARRAY_SIZE(output_comps); ++u) {
enum pipe_swizzle s = desc->swizzle[u];
if (s <= PIPE_SWIZZLE_W)
output_comps[u] = unpacked_comps[s];
else if (s == PIPE_SWIZZLE_0)
output_comps[u] = zero;
else if (s == PIPE_SWIZZLE_1)
output_comps[u] = one;
else
UNREACHABLE("");
}
return nir_vec(b, output_comps, components_needed);
}
static inline bool is_processed(nir_intrinsic_instr *intr)
{
nir_alu_type type;
if (nir_intrinsic_has_src_type(intr))
type = nir_intrinsic_src_type(intr);
else if (nir_intrinsic_has_dest_type(intr))
type = nir_intrinsic_dest_type(intr);
else
return true;
return nir_alu_type_get_base_type(type) == nir_type_invalid;
}
static bool lower_pfo_store(nir_builder *b,
nir_intrinsic_instr *intr,
struct pfo_state *state)
{
/* Skip stores we've already processed. */
if (is_processed(intr)) {
util_dynarray_append(&state->stores, intr);
return false;
}
nir_def *input = intr->src[0].ssa;
nir_src *offset = &intr->src[1];
assert(nir_src_as_uint(*offset) == 0);
ASSERTED unsigned bit_size = input->bit_size;
assert(bit_size == 32);
unsigned component = nir_intrinsic_component(intr);
assert(!component);
struct nir_io_semantics io_semantics = nir_intrinsic_io_semantics(intr);
gl_frag_result location = io_semantics.location;
b->cursor = nir_before_instr(&intr->instr);
enum pipe_format format = state->fs->output_formats[location];
if (format == PIPE_FORMAT_NONE) {
nir_instr_remove(&intr->instr);
return true;
}
format = to_pbe_format(b, format, &input);
nir_alu_type src_type = nir_intrinsic_src_type(intr);
nir_def *output = pack_to_format(b, input, src_type, format);
/* Emit and track the new store. */
nir_intrinsic_instr *store =
nir_store_output(b,
output,
offset->ssa,
.base = nir_intrinsic_base(intr),
.write_mask = BITFIELD_MASK(output->num_components),
.src_type = nir_type_invalid | 32,
.component = component,
.io_semantics = io_semantics,
.io_xfb = nir_intrinsic_io_xfb(intr));
util_dynarray_append(&state->stores, store);
/* Update the type of the stored variable. */
nir_variable *var =
nir_find_variable_with_location(b->shader, nir_var_shader_out, location);
assert(var);
var->type = glsl_uvec_type(output->num_components);
nir_instr_remove(&intr->instr);
return true;
}
static bool lower_pfo_load(nir_builder *b,
nir_intrinsic_instr *intr,
struct pfo_state *state)
{
/* Skip loads we've already processed. */
if (is_processed(intr)) {
util_dynarray_append(&state->loads, intr);
return false;
}
unsigned base = nir_intrinsic_base(intr);
nir_src *offset = &intr->src[0];
assert(nir_src_as_uint(*offset) == 0);
struct nir_io_semantics io_semantics = nir_intrinsic_io_semantics(intr);
gl_frag_result location = io_semantics.location;
enum pipe_format format;
/* Special case for input attachments. */
if (location == FRAG_RESULT_COLOR) {
format = state->fs->ia_formats[base];
} else {
assert(location >= FRAG_RESULT_DATA0);
assert(!base);
format = state->fs->output_formats[location];
}
nir_def *repl;
if (format == PIPE_FORMAT_NONE)
repl = nir_undef(b, intr->def.num_components, intr->def.bit_size);
else {
format = to_pbe_format(b, format, NULL);
nir_def *packed_comps[4];
for (unsigned c = 0; c < ARRAY_SIZE(packed_comps); ++c) {
packed_comps[c] = nir_load_output(b,
1,
32,
offset->ssa,
.base = base,
.component = c,
.dest_type = nir_type_invalid | 32,
.io_semantics = io_semantics);
nir_intrinsic_instr *load = nir_def_as_intrinsic(packed_comps[c]);
util_dynarray_append(&state->loads, load);
}
nir_alu_type dest_type = nir_intrinsic_dest_type(intr);
repl = unpack_from_format(b,
packed_comps,
dest_type,
format,
intr->def.num_components);
}
nir_def_rewrite_uses(&intr->def, repl);
nir_instr_remove(&intr->instr);
return true;
}
/**
* \brief Lowers a PFO-related instruction.
*
* \param[in] b NIR builder.
* \param[in] intr NIR intrinsic instruction.
* \param[in] cb_data User callback data.
* \return True if progress was made.
*/
static bool lower_pfo(nir_builder *b, nir_intrinsic_instr *intr, void *cb_data)
{
struct pfo_state *state = cb_data;
b->cursor = nir_before_instr(&intr->instr);
switch (intr->intrinsic) {
case nir_intrinsic_store_output: {
nir_io_semantics sem = nir_intrinsic_io_semantics(intr);
if (sem.location >= FRAG_RESULT_DATA0)
return lower_pfo_store(b, intr, state);
if (sem.location == FRAG_RESULT_DEPTH) {
assert(!state->depth_feedback_src);
state->depth_feedback_src = nir_fsat(b, intr->src[0].ssa);
nir_instr_remove(&intr->instr);
return true;
}
if (sem.location == FRAG_RESULT_SAMPLE_MASK) {
nir_def *smp_msk =
nir_ishl(b, nir_imm_int(b, 1), nir_load_sample_id(b));
smp_msk = nir_iand(b, smp_msk, nir_load_sample_mask_in(b));
smp_msk = nir_iand(b, smp_msk, intr->src[0].ssa);
nir_def *cond = nir_ieq_imm(b, smp_msk, 0);
state->has_discards = true;
state->has_sample_check = true;
nir_def *val = nir_load_reg(b, state->discard_cond_reg);
val = nir_ior(b, val, cond);
state->last_discard_store =
nir_build_store_reg(b, val, state->discard_cond_reg);
nir_instr_remove(&intr->instr);
return true;
}
UNREACHABLE("");
}
case nir_intrinsic_load_output:
return lower_pfo_load(b, intr, state);
case nir_intrinsic_demote:
state->has_discards = true;
state->last_discard_store =
nir_build_store_reg(b, nir_imm_true(b), state->discard_cond_reg);
nir_instr_remove(&intr->instr);
return true;
case nir_intrinsic_demote_if: {
state->has_discards = true;
nir_def *val = nir_load_reg(b, state->discard_cond_reg);
val = nir_ior(b, val, intr->src[0].ssa);
state->last_discard_store =
nir_build_store_reg(b, val, state->discard_cond_reg);
nir_instr_remove(&intr->instr);
return true;
}
default:
break;
}
return false;
}
static bool lower_isp_fb(nir_builder *b, struct pfo_state *state)
{
if (b->shader->info.internal)
return false;
bool has_depth_feedback = !!state->depth_feedback_src;
if (b->shader->info.writes_memory && !has_depth_feedback) {
nir_variable *var_pos = nir_get_variable_with_location(b->shader,
nir_var_shader_in,
VARYING_SLOT_POS,
glsl_vec4_type());
var_pos->data.interpolation = INTERP_MODE_NOPERSPECTIVE;
b->cursor = nir_before_block(
nir_start_block(nir_shader_get_entrypoint(b->shader)));
state->depth_feedback_src =
nir_load_input(b,
1,
32,
nir_imm_int(b, 0),
.component = 2,
.dest_type = nir_type_float32,
.io_semantics = (nir_io_semantics){
.location = VARYING_SLOT_POS,
.num_slots = 1,
});
has_depth_feedback = true;
}
if (!state->has_sample_check) {
b->cursor = nir_after_instr(&state->last_discard_store->instr);
nir_def *smp_msk = nir_ishl(b, nir_imm_int(b, 1), nir_load_sample_id(b));
smp_msk = nir_iand(b, smp_msk, nir_load_sample_mask_in(b));
nir_def *cond = nir_ieq_imm(b, smp_msk, 0);
nir_def *val = nir_load_reg(b, state->discard_cond_reg);
val = nir_ior(b, val, cond);
state->last_discard_store =
nir_build_store_reg(b, val, state->discard_cond_reg);
state->has_sample_check = true;
state->has_discards = true;
}
/* Insert isp feedback instruction before the first store,
* or if there are no stores, at the end.
*/
if (state->stores.size > 0)
b->cursor = nir_before_instr(
&(*(nir_intrinsic_instr **)util_dynarray_begin(&state->stores))->instr);
else
b->cursor = nir_after_block(
nir_impl_last_block(nir_shader_get_entrypoint(b->shader)));
nir_def *undef = nir_undef(b, 1, 32);
nir_isp_feedback_pco(
b,
state->has_discards ? nir_i2b(b, nir_load_reg(b, state->discard_cond_reg))
: undef,
has_depth_feedback ? state->depth_feedback_src : undef);
state->fs->uses.discard = state->has_discards;
state->fs->uses.depth_feedback = has_depth_feedback;
return true;
}
static bool sink_outputs(nir_shader *shader, struct pfo_state *state)
{
bool progress = false;
nir_instr *after_instr = nir_block_last_instr(
nir_impl_last_block(nir_shader_get_entrypoint(shader)));
util_dynarray_foreach (&state->stores, nir_intrinsic_instr *, store) {
nir_instr *instr = &(*store)->instr;
progress |= nir_instr_move(nir_after_instr(after_instr), instr);
after_instr = instr;
}
return progress;
}
static bool z_replicate(nir_shader *shader, struct pfo_state *state)
{
if (shader->info.internal || state->fs->z_replicate == ~0u)
return false;
assert(!nir_find_variable_with_location(shader,
nir_var_shader_out,
state->fs->z_replicate));
nir_create_variable_with_location(shader,
nir_var_shader_out,
state->fs->z_replicate,
glsl_float_type());
if (!state->depth_feedback_src) {
nir_variable *var_pos = nir_get_variable_with_location(shader,
nir_var_shader_in,
VARYING_SLOT_POS,
glsl_vec4_type());
var_pos->data.interpolation = INTERP_MODE_NOPERSPECTIVE;
nir_builder b = nir_builder_at(
nir_before_block(nir_start_block(nir_shader_get_entrypoint(shader))));
state->depth_feedback_src =
nir_load_input(&b,
1,
32,
nir_imm_int(&b, 0),
.component = 2,
.dest_type = nir_type_float32,
.io_semantics = (nir_io_semantics){
.location = VARYING_SLOT_POS,
.num_slots = 1,
});
}
nir_builder b = nir_builder_at(
nir_after_block(nir_impl_last_block(nir_shader_get_entrypoint(shader))));
nir_store_output(&b,
state->depth_feedback_src,
nir_imm_int(&b, 0),
.write_mask = 1,
.src_type = nir_type_invalid | 32,
.io_semantics = (nir_io_semantics){
.location = state->fs->z_replicate,
.num_slots = 1,
});
return true;
}
static bool lower_demote_samples(nir_builder *b,
nir_intrinsic_instr *intr,
UNUSED void *cb_data)
{
if (intr->intrinsic != nir_intrinsic_demote_samples)
return false;
b->cursor = nir_before_instr(&intr->instr);
nir_def *to_keep = nir_u2u32(b, nir_inot(b, intr->src[0].ssa));
nir_def *sample_mask = nir_load_savmsk_vm_pco(b);
nir_def *current_mask =
nir_ishl(b, nir_imm_int(b, 1), nir_load_sample_id(b));
nir_def *cond = nir_iand(b, to_keep, nir_iand(b, sample_mask, current_mask));
nir_demote_if(b, nir_ieq_imm(b, cond, 0));
nir_instr_remove(&intr->instr);
return true;
}
bool pco_nir_lower_alpha_to_coverage(nir_shader *shader)
{
if (shader->info.internal)
return false;
nir_builder b = nir_builder_create(nir_shader_get_entrypoint(shader));
b.cursor =
nir_before_block(nir_start_block(nir_shader_get_entrypoint(shader)));
nir_def *a2c_enabled = nir_ine_imm(
&b,
nir_ubitfield_extract_imm(&b, nir_load_fs_meta_pco(&b), 25, 1),
0);
nir_lower_alpha_to_coverage(shader, true, a2c_enabled);
nir_shader_intrinsics_pass(shader,
lower_demote_samples,
nir_metadata_control_flow,
NULL);
return true;
}
static bool lower_alpha_to_one(nir_builder *b,
nir_intrinsic_instr *intr,
UNUSED void *cb_data)
{
if (intr->intrinsic != nir_intrinsic_store_output)
return false;
gl_frag_result location = nir_intrinsic_io_semantics(intr).location;
if (!(location >= FRAG_RESULT_DATA0 && location < FRAG_RESULT_MAX))
return false;
nir_src *input_src = &intr->src[0];
nir_def *input = input_src->ssa;
nir_src *offset = &intr->src[1];
assert(nir_src_as_uint(*offset) == 0);
/* Skip color write that don't include alpha. */
if (input->num_components != 4)
return false;
b->cursor = nir_before_instr(&intr->instr);
/* TODO: define or other way of representing bit 0 of metadata... */
nir_def *alpha_to_one_enabled =
nir_ine_imm(b,
nir_ubitfield_extract_imm(b, nir_load_fs_meta_pco(b), 0, 1),
0);
nir_def *alpha = nir_bcsel(b,
alpha_to_one_enabled,
nir_imm_float(b, 1.0f),
nir_channel(b, input, 3));
nir_src_rewrite(input_src, nir_vector_insert_imm(b, input, alpha, 3));
return true;
}
static bool is_load_sample_mask(const nir_instr *instr,
UNUSED const void *cb_data)
{
if (instr->type != nir_instr_type_intrinsic)
return false;
nir_intrinsic_instr *intr = nir_instr_as_intrinsic(instr);
return intr->intrinsic == nir_intrinsic_load_sample_mask_in;
}
static bool lower_load_sample_mask(nir_builder *b,
nir_intrinsic_instr *intr,
UNUSED void *cb_data)
{
if (intr->intrinsic != nir_intrinsic_load_sample_mask_in)
return false;
b->cursor = nir_before_instr(&intr->instr);
nir_def *smp_msk =
nir_ubitfield_extract_imm(b, nir_load_fs_meta_pco(b), 9, 16);
smp_msk = nir_iand(b, smp_msk, nir_load_savmsk_vm_pco(b));
nir_def_rewrite_uses(&intr->def, smp_msk);
nir_instr_remove(&intr->instr);
return true;
}
static bool lower_color_write_enable(nir_builder *b,
nir_intrinsic_instr *intr,
UNUSED void *cb_data)
{
if (intr->intrinsic != nir_intrinsic_store_output)
return false;
gl_frag_result location = nir_intrinsic_io_semantics(intr).location;
if (!(location >= FRAG_RESULT_DATA0 && location < FRAG_RESULT_MAX))
return false;
nir_src *input_src = &intr->src[0];
nir_def *input = input_src->ssa;
nir_def *offset = intr->src[1].ssa;
struct nir_io_semantics io_semantics = nir_intrinsic_io_semantics(intr);
unsigned color_write_index = io_semantics.location - FRAG_RESULT_DATA0;
io_semantics.fb_fetch_output = true;
b->cursor = nir_before_instr(&intr->instr);
/* TODO: nir op that returns bool based on whether a bit is set. */
/* TODO: define for 1 */
nir_def *color_write_enabled =
nir_ine_imm(b,
nir_ubitfield_extract_imm(b,
nir_load_fs_meta_pco(b),
1 + color_write_index,
1),
0);
nir_def *prev_input =
nir_load_output(b,
input->num_components,
input->bit_size,
offset,
.base = nir_intrinsic_base(intr),
.range = nir_intrinsic_range(intr),
.component = nir_intrinsic_component(intr),
.dest_type = nir_intrinsic_src_type(intr),
.io_semantics = io_semantics);
nir_src_rewrite(input_src,
nir_bcsel(b, color_write_enabled, input, prev_input));
return true;
}
/**
* \brief Per-fragment output pass.
*
* \param[in,out] shader NIR shader.
* \param[in,out] fs Fragment shader-specific data.
* \return True if the pass made progress.
*/
bool pco_nir_pfo(nir_shader *shader, pco_fs_data *fs)
{
assert(shader->info.stage == MESA_SHADER_FRAGMENT);
nir_builder b = nir_builder_create(nir_shader_get_entrypoint(shader));
b.cursor =
nir_before_block(nir_start_block(nir_shader_get_entrypoint(shader)));
struct pfo_state state = {
.fs = fs,
.discard_cond_reg = nir_decl_reg(&b, 1, 1, 0),
};
state.last_discard_store =
nir_build_store_reg(&b, nir_imm_false(&b), state.discard_cond_reg);
state.loads = UTIL_DYNARRAY_INIT;
state.stores = UTIL_DYNARRAY_INIT;
bool progress = false;
/* TODO: instead of doing multiple passes, probably better to just cache all
* the stores
*/
if (!shader->info.internal) {
progress |= nir_shader_intrinsics_pass(shader,
lower_alpha_to_one,
nir_metadata_control_flow,
&state);
}
if (fs->meta_present.color_write_enable) {
progress |= nir_shader_intrinsics_pass(shader,
lower_color_write_enable,
nir_metadata_control_flow,
NULL);
}
progress |= nir_shader_intrinsics_pass(shader,
lower_pfo,
nir_metadata_control_flow,
&state);
progress |= lower_isp_fb(&b, &state);
progress |= sink_outputs(shader, &state);
progress |= z_replicate(shader, &state);
progress |= nir_shader_intrinsics_pass(shader,
lower_load_sample_mask,
nir_metadata_control_flow,
NULL);
util_dynarray_fini(&state.stores);
util_dynarray_fini(&state.loads);
return progress;
}
static bool lower_pvi(nir_builder *b, nir_intrinsic_instr *intr, void *cb_data)
{
struct pvi_state *state = cb_data;
if (intr->intrinsic != nir_intrinsic_load_input)
return false;
if (is_processed(intr))
return false;
unsigned start_comp = nir_intrinsic_component(intr);
unsigned num_comps = intr->def.num_components;
ASSERTED gl_vert_attrib location = nir_intrinsic_io_semantics(intr).location;
assert(location >= VERT_ATTRIB_GENERIC0 &&
location <= VERT_ATTRIB_GENERIC15);
ASSERTED nir_src *offset = &intr->src[0];
assert(nir_src_as_uint(*offset) == 0);
nir_def *attrib = state->attribs[location - VERT_ATTRIB_GENERIC0];
assert(attrib);
b->cursor = nir_before_instr(&intr->instr);
attrib = nir_channels(b, attrib, BITFIELD_RANGE(start_comp, num_comps));
nir_def_rewrite_uses(&intr->def, attrib);
nir_instr_remove(&intr->instr);
return true;
}
/**
* \brief Per-vertex input pass.
*
* \param[in,out] shader NIR shader.
* \param[in,out] vs Vertex shader-specific data.
* \return True if the pass made progress.
*/
bool pco_nir_pvi(nir_shader *shader, pco_vs_data *vs)
{
assert(shader->info.stage == MESA_SHADER_VERTEX);
struct pvi_state state = { .vs = vs };
nir_builder b = nir_builder_at(
nir_before_block(nir_start_block(nir_shader_get_entrypoint(shader))));
for (unsigned u = 0; u < ARRAY_SIZE(state.attribs); ++u) {
gl_vert_attrib location = u + VERT_ATTRIB_GENERIC0;
enum pipe_format format = vs->attrib_formats[location];
if (format == PIPE_FORMAT_NONE)
continue;
/* Update the type of the stored variable, remove any fractional vars. */
nir_variable *var = NULL;
nir_alu_type base_type = 0;
nir_foreach_variable_with_modes_safe (iter_var,
shader,
nir_var_shader_in) {
if (iter_var->data.location != location)
continue;
if (!base_type)
base_type = nir_get_nir_type_for_glsl_type(iter_var->type);
#ifndef NDEBUG
else
assert(base_type == nir_get_nir_type_for_glsl_type(iter_var->type));
#endif /* NDEBUG */
if (!iter_var->data.location_frac) {
assert(!var);
var = iter_var;
continue;
}
exec_node_remove(&iter_var->node);
}
if (!var) {
if (!base_type)
continue;
/* An attrib var was found but was fractional so we dropped it. */
var = nir_variable_create(shader, nir_var_shader_in, NULL, NULL);
var->data.location = location;
}
unsigned format_dwords =
DIV_ROUND_UP(util_format_get_blocksize(format), sizeof(uint32_t));
var->type = glsl_uvec_type(format_dwords);
nir_def *packed_comps[4];
for (unsigned c = 0; c < ARRAY_SIZE(packed_comps); ++c) {
packed_comps[c] = nir_load_input(&b,
1,
32,
nir_imm_int(&b, 0),
.range = 1,
.component = c,
.dest_type = nir_type_invalid | 32,
.io_semantics = (nir_io_semantics){
.location = location,
.num_slots = 1,
});
}
state.attribs[u] =
unpack_from_format(&b, packed_comps, base_type, format, 4);
}
nir_shader_intrinsics_pass(shader,
lower_pvi,
nir_metadata_control_flow,
&state);
return true;
}
/**
* \brief Checks if the point size is written.
*
* \param[in] b NIR builder.
* \param[in] intr NIR intrinsic instruction.
* \param[in] cb_data User callback data.
* \return True if the instruction was lowered.
*/
static bool
check_psiz_write(nir_builder *b, nir_intrinsic_instr *intr, void *cb_data)
{
if (intr->intrinsic != nir_intrinsic_store_output)
return false;
bool *writes_psiz = cb_data;
struct nir_io_semantics io_semantics = nir_intrinsic_io_semantics(intr);
*writes_psiz |= (io_semantics.location == VARYING_SLOT_PSIZ);
return false;
}
/**
* \brief Vertex shader point size pass.
*
* \param[in,out] shader NIR shader.
* \return True if the pass made progress.
*/
bool pco_nir_point_size(nir_shader *shader)
{
assert(shader->info.stage == MESA_SHADER_VERTEX);
if (shader->info.internal)
return false;
bool writes_psiz = false;
nir_shader_intrinsics_pass(shader,
check_psiz_write,
nir_metadata_all,
&writes_psiz);
/* Nothing to do if the shader already writes the point size. */
if (writes_psiz)
return false;
/* Create a point size variable if there isn't one. */
nir_get_variable_with_location(shader,
nir_var_shader_out,
VARYING_SLOT_PSIZ,
glsl_float_type());
/* Add a point size write. */
nir_builder b = nir_builder_at(
nir_after_block(nir_impl_last_block(nir_shader_get_entrypoint(shader))));
nir_store_output(&b,
nir_imm_float(&b, PVR_POINT_SIZE_RANGE_MIN),
nir_imm_int(&b, 0),
.base = 0,
.range = 1,
.write_mask = 1,
.component = 0,
.src_type = nir_type_float32,
.io_semantics = (nir_io_semantics){
.location = VARYING_SLOT_PSIZ,
.num_slots = 1,
});
return true;
}
static bool lower_front_face(nir_builder *b, nir_intrinsic_instr *intr)
{
nir_def *face_ccw = nir_load_face_ccw_pco(b);
nir_def *front_face = nir_ieq_imm(b, face_ccw, 0);
nir_def *ff_op = nir_load_front_face_op_pco(b);
nir_def *ff_elems[] = {
[PCO_FRONT_FACE_OP_NOP] = front_face,
[PCO_FRONT_FACE_OP_SWAP] = nir_inot(b, front_face),
[PCO_FRONT_FACE_OP_TRUE] = nir_imm_true(b),
};
nir_def *ff_selected =
nir_select_from_ssa_def_array(b, ff_elems, ARRAY_SIZE(ff_elems), ff_op);
nir_def_rewrite_uses(&intr->def, ff_selected);
nir_instr_remove(&intr->instr);
return true;
}
static bool
lower_fs_intr(nir_builder *b, nir_intrinsic_instr *intr, UNUSED void *cb_data)
{
switch (intr->intrinsic) {
case nir_intrinsic_load_front_face:
b->cursor = nir_before_instr(&intr->instr);
return lower_front_face(b, intr);
default:
break;
}
return false;
}
bool pco_nir_lower_fs_intrinsics(nir_shader *shader)
{
assert(shader->info.stage == MESA_SHADER_FRAGMENT);
return nir_shader_intrinsics_pass(shader,
lower_fs_intr,
nir_metadata_control_flow,
NULL);
}
static bool
lower_vs_intr(nir_builder *b, nir_intrinsic_instr *intr, UNUSED void *cb_data)
{
b->cursor = nir_before_instr(&intr->instr);
switch (intr->intrinsic) {
/* First vs base vertex is handled in the PDS, so they're equivalent. */
case nir_intrinsic_load_first_vertex:
nir_def_replace(&intr->def, nir_load_base_vertex(b));
nir_instr_free(&intr->instr);
return true;
default:
break;
}
return false;
}
bool pco_nir_lower_vs_intrinsics(nir_shader *shader)
{
assert(shader->info.stage == MESA_SHADER_VERTEX);
return nir_shader_intrinsics_pass(shader,
lower_vs_intr,
nir_metadata_control_flow,
NULL);
}
bool pco_nir_lower_clip_cull_vars(nir_shader *shader)
{
if (shader->info.internal)
return false;
unsigned clip_cull_comps = shader->info.clip_distance_array_size +
shader->info.cull_distance_array_size;
if (!clip_cull_comps)
return false;
/* Remove the old variables. */
const gl_varying_slot clip_cull_locations[] = {
VARYING_SLOT_CLIP_DIST0,
VARYING_SLOT_CLIP_DIST1,
};
nir_variable *var;
for (unsigned u = 0; u < ARRAY_SIZE(clip_cull_locations); ++u) {
gl_varying_slot location = clip_cull_locations[u];
while ((var = nir_find_variable_with_location(shader,
nir_var_shader_out,
location))) {
exec_node_remove(&var->node);
}
}
/* Create new variables. */
nir_create_variable_with_location(shader,
nir_var_shader_out,
VARYING_SLOT_CLIP_DIST0,
glsl_vec_type(MIN2(clip_cull_comps, 4)));
if (clip_cull_comps > 4) {
nir_create_variable_with_location(shader,
nir_var_shader_out,
VARYING_SLOT_CLIP_DIST1,
glsl_vec_type(clip_cull_comps - 4));
}
nir_metadata_invalidate(shader);
return true;
}
static bool
clone_clip_cull_stores(nir_builder *b, nir_intrinsic_instr *intr, void *data)
{
if (intr->intrinsic != nir_intrinsic_store_deref)
return false;
nir_deref_instr *deref = nir_src_as_deref(intr->src[0]);
if (deref->deref_type != nir_deref_type_array)
return false;
nir_variable *var = nir_deref_instr_get_variable(deref);
if (var->data.location != VARYING_SLOT_CLIP_DIST0 &&
var->data.location != VARYING_SLOT_CLIP_DIST1)
return false;
b->cursor = nir_after_instr(&intr->instr);
unsigned var_index = var->data.location - VARYING_SLOT_CLIP_DIST0;
nir_def *index =
nir_iadd_imm(b, deref->arr.index.ssa, var->data.location_frac);
index = nir_iadd_imm(b, index, var_index * 4);
nir_variable *clone_var = data;
nir_store_array_var(b, clone_var, index, intr->src[1].ssa, 1);
return true;
}
static bool
swap_clip_cull_load(nir_builder *b, nir_intrinsic_instr *intr, void *cb_data)
{
if (intr->intrinsic != nir_intrinsic_load_deref)
return false;
nir_deref_instr *deref = nir_src_as_deref(intr->src[0]);
if (deref->deref_type != nir_deref_type_array)
return false;
nir_variable *var = nir_deref_instr_get_variable(deref);
if (var->data.location != VARYING_SLOT_CLIP_DIST0 &&
var->data.location != VARYING_SLOT_CLIP_DIST1)
return false;
b->cursor = nir_before_instr(&intr->instr);
unsigned var_index = var->data.location - VARYING_SLOT_CLIP_DIST0;
nir_def *index =
nir_iadd_imm(b, deref->arr.index.ssa, var->data.location_frac);
index = nir_iadd_imm(b, index, var_index * 4);
nir_variable *clone_var = cb_data;
nir_def *loaded_clone_var = nir_load_array_var(b, clone_var, index);
nir_def_rewrite_uses(&intr->def, loaded_clone_var);
nir_instr_remove(&intr->instr);
return true;
}
bool pco_nir_link_clip_cull_vars(nir_shader *producer, nir_shader *consumer)
{
if (producer->info.stage != MESA_SHADER_VERTEX ||
consumer->info.stage != MESA_SHADER_FRAGMENT) {
return false;
}
unsigned clip_cull_comps = consumer->info.clip_distance_array_size +
consumer->info.cull_distance_array_size;
/* Skip if clip/cull comps aren't actually consumed. */
if (!clip_cull_comps)
return false;
const glsl_type *clone_var_type =
glsl_array_type(glsl_float_type(), clip_cull_comps, 0);
/* Find unused varying slot to use and create the variables. */
gl_varying_slot clone_slot = VARYING_SLOT_VAR0;
nir_foreach_shader_out_variable (var, producer) {
clone_slot = MAX2(clone_slot, var->data.location + 1);
}
assert(clone_slot < VARYING_SLOT_MAX);
nir_variable *clone_var =
nir_variable_create(producer, nir_var_shader_out, clone_var_type, NULL);
clone_var->data.location = clone_slot;
nir_shader_intrinsics_pass(producer,
clone_clip_cull_stores,
nir_metadata_block_index | nir_metadata_dominance,
clone_var);
clone_var =
nir_variable_create(consumer, nir_var_shader_in, clone_var_type, NULL);
clone_var->data.location = clone_slot;
nir_shader_intrinsics_pass(consumer,
swap_clip_cull_load,
nir_metadata_control_flow,
clone_var);
return true;
}
static nir_def *alu_iter(nir_builder *b,
nir_def *coords,
unsigned component,
struct nir_io_semantics io_semantics)
{
nir_def *coeffs = nir_load_fs_coeffs_pco(b,
.component = component,
.io_semantics = io_semantics);
nir_def *result = nir_ffma_weak(b,
nir_channel(b, coeffs, 1),
nir_channel(b, coords, 1),
nir_channel(b, coeffs, 2));
result =
nir_ffma_weak(b, nir_channel(b, coeffs, 0), nir_channel(b, coords, 0), result);
return result;
}
static bool
lower_sample_pos(nir_builder *b, nir_intrinsic_instr *intr, pco_fs_data *fs)
{
b->cursor = nir_before_instr(&intr->instr);
nir_def *msaa_samples = nir_bit_count(
b,
nir_u2u32(b, nir_alpha_to_coverage(b, nir_imm_float(b, 1.0f))));
nir_def *sample_id = intr->intrinsic == nir_intrinsic_load_sample_pos
? nir_load_sample_id(b)
: intr->src[0].ssa;
nir_def *dword_index =
nir_ishr_imm(b, nir_iadd(b, msaa_samples, sample_id), 2);
nir_def *packed_sample_location =
nir_load_packed_sample_location_pco(b, dword_index);
fs->uses.sample_locations = true;
nir_def *byte_index = nir_iand_imm(b, sample_id, 0b11);
packed_sample_location =
nir_extract_u8(b, packed_sample_location, byte_index);
nir_def *sample_location =
nir_vec2(b,
nir_ubitfield_extract_imm(b, packed_sample_location, 0, 4),
nir_ubitfield_extract_imm(b, packed_sample_location, 4, 4));
sample_location = nir_u2f32(b, sample_location);
sample_location = nir_fdiv_imm(b, sample_location, 16.0f);
sample_location = nir_bcsel(b,
nir_ieq_imm(b, msaa_samples, 1),
nir_imm_vec2(b, 0.5f, 0.5f),
sample_location);
nir_def_replace(&intr->def, sample_location);
nir_instr_free(&intr->instr);
return true;
}
static bool
lower_interp(nir_builder *b, nir_intrinsic_instr *intr, void *cb_data)
{
pco_fs_data *fs = cb_data;
b->cursor = nir_before_instr(&intr->instr);
switch (intr->intrinsic) {
case nir_intrinsic_load_sample_pos:
case nir_intrinsic_load_sample_pos_from_id:
return lower_sample_pos(b, intr, fs);
case nir_intrinsic_load_interpolated_input:
break;
default:
return false;
}
nir_intrinsic_instr *bary = nir_src_as_intrinsic(intr->src[0]);
assert(bary);
/* Skip cases that don't need handling. */
if (bary->intrinsic != nir_intrinsic_load_barycentric_at_offset)
return false;
assert(nir_src_as_uint(intr->src[1]) == 0);
nir_def *coords = nir_load_tile_coord_pco(b, 2);
coords = nir_fadd(b, coords, bary->src[0].ssa);
enum glsl_interp_mode interp_mode = nir_intrinsic_interp_mode(bary);
nir_def *rhw = alu_iter(b,
coords,
3,
(struct nir_io_semantics){
.location = VARYING_SLOT_POS,
.num_slots = 1,
});
nir_def *comps[4];
for (unsigned u = 0; u < intr->def.num_components; ++u) {
comps[u] = alu_iter(b, coords, u, nir_intrinsic_io_semantics(intr));
if (interp_mode != INTERP_MODE_NOPERSPECTIVE)
comps[u] = nir_fdiv(b, comps[u], rhw);
}
nir_def *repl = nir_vec(b, comps, intr->def.num_components);
nir_def_replace(&intr->def, repl);
nir_instr_free(&intr->instr);
return true;
}
bool pco_nir_lower_interpolation(nir_shader *shader, pco_fs_data *fs)
{
bool progress = false;
progress |= nir_opt_barycentric(shader, true);
progress |= nir_shader_intrinsics_pass(shader,
lower_interp,
nir_metadata_control_flow,
fs);
return progress;
}
static bool lower_load_view_index_fs(struct nir_builder *b,
nir_intrinsic_instr *intr,
void *cb_data)
{
if (intr->intrinsic != nir_intrinsic_load_view_index)
return false;
nir_variable *view_index_var = cb_data;
b->cursor = nir_before_instr(&intr->instr);
nir_def_replace(&intr->def, nir_load_var(b, view_index_var));
nir_instr_free(&intr->instr);
return true;
}
bool pco_nir_link_multiview(nir_shader *producer,
nir_shader *consumer,
pco_data *consumer_data)
{
if (producer->info.stage != MESA_SHADER_VERTEX ||
consumer->info.stage != MESA_SHADER_FRAGMENT ||
!consumer_data->common.multiview) {
return false;
}
/* Find unused varying slot for the view index. */
gl_varying_slot view_index_slot = VARYING_SLOT_VAR0;
nir_foreach_shader_out_variable (var, producer) {
view_index_slot = MAX2(view_index_slot, var->data.location + 1);
}
assert(view_index_slot < VARYING_SLOT_MAX);
consumer_data->fs.view_index_slot = view_index_slot;
/* Create output variable in the producer. */
nir_variable *view_index_var = nir_variable_create(producer,
nir_var_shader_out,
glsl_uint_type(),
"view_index");
view_index_var->data.location = view_index_slot;
view_index_var->data.interpolation = INTERP_MODE_FLAT;
view_index_var->data.always_active_io = true;
/* Store view index in the producer. */
nir_builder b = nir_builder_at(nir_after_block(
nir_impl_last_block(nir_shader_get_entrypoint(producer))));
nir_store_var(&b, view_index_var, nir_load_view_index(&b), 1);
/* Create input variable in the consumer. */
view_index_var = nir_variable_create(consumer,
nir_var_shader_in,
glsl_uint_type(),
"view_index");
view_index_var->data.location = view_index_slot;
view_index_var->data.interpolation = INTERP_MODE_FLAT;
view_index_var->data.always_active_io = true;
/* Lower view index loads in the consumer. */
nir_shader_intrinsics_pass(consumer,
lower_load_view_index_fs,
nir_metadata_all,
view_index_var);
return true;
}