asahi: Wire up geometry shaders

- Compile GS with linked VS and auxiliary programs
- Dispatch GS as compute programs + an indirect draw with the GS copy program
- Use passthrough GS to implement XFB, replacing old XFB impl.

Signed-off-by: Alyssa Rosenzweig <alyssa@rosenzweig.io>
Part-of: <https://gitlab.freedesktop.org/mesa/mesa/-/merge_requests/26056>
This commit is contained in:
Alyssa Rosenzweig
2023-10-09 07:52:08 -04:00
committed by Marge Bot
parent fe7650bcf7
commit c6a118b654
10 changed files with 852 additions and 624 deletions
+1 -1
View File
@@ -2931,7 +2931,7 @@ agx_compile_shader_nir(nir_shader *nir, struct agx_shader_key *key,
if (nir->info.stage == MESA_SHADER_FRAGMENT)
out->tag_write_disable = !nir->info.writes_memory;
bool needs_libagx = false;
bool needs_libagx = nir->info.stage == MESA_SHADER_GEOMETRY;
/* Late tilebuffer lowering creates multisampled image stores */
NIR_PASS(needs_libagx, nir, agx_nir_lower_multisampled_image_store);
+4
View File
@@ -13,6 +13,7 @@ libasahi_lib_files = files(
'agx_meta.c',
'agx_tilebuffer.c',
'agx_nir_lower_alpha.c',
'agx_nir_lower_gs.c',
'agx_nir_lower_msaa.c',
'agx_nir_lower_sample_intrinsics.c',
'agx_nir_lower_tilebuffer.c',
@@ -28,6 +29,8 @@ libasahi_decode_files = files(
libagx_shader_files = files(
'shaders/libagx.h',
'shaders/geometry.cl',
'shaders/geometry.h',
'shaders/texture.cl',
)
@@ -75,6 +78,7 @@ libagx_shaders = custom_target(
prepended_input_args, '-o', '@OUTPUT@', '--',
'-cl-std=cl2.0', '-D__OPENCL_VERSION__=200',
'-I' + join_paths(meson.current_source_dir(), '.'),
'-I' + join_paths(meson.current_source_dir(), '../../'),
'-I' + join_paths(meson.current_source_dir(), 'shaders'),
'-I' + join_paths(meson.current_build_dir(), '.'),
],
+1
View File
@@ -133,6 +133,7 @@ agx_batch_init(struct agx_context *ctx,
batch->clear_depth = 0;
batch->clear_stencil = 0;
batch->varyings = 0;
batch->geometry_state = 0;
batch->any_draws = false;
batch->initialized = false;
batch->draws = 0;
+2
View File
@@ -19,6 +19,8 @@ agx_blitter_save(struct agx_context *ctx, struct blitter_context *blitter,
util_blitter_save_vertex_elements(blitter, ctx->attributes);
util_blitter_save_vertex_shader(blitter,
ctx->stage[PIPE_SHADER_VERTEX].shader);
util_blitter_save_geometry_shader(blitter,
ctx->stage[PIPE_SHADER_GEOMETRY].shader);
util_blitter_save_rasterizer(blitter, ctx->rast);
util_blitter_save_viewport(blitter, &ctx->viewport);
util_blitter_save_scissor(blitter, &ctx->scissor);
@@ -35,6 +35,8 @@ agx_disk_cache_compute_key(struct disk_cache *cache,
int key_size;
if (uncompiled->type == PIPE_SHADER_VERTEX)
key_size = sizeof(shader_key->vs);
else if (uncompiled->type == PIPE_SHADER_GEOMETRY)
key_size = sizeof(shader_key->gs);
else if (uncompiled->type == PIPE_SHADER_FRAGMENT)
key_size = sizeof(shader_key->fs);
else if (uncompiled->type == PIPE_SHADER_COMPUTE)
@@ -66,6 +68,10 @@ agx_disk_cache_store(struct disk_cache *cache,
if (!cache)
return;
/* TODO: Support caching GS */
if (uncompiled->type == PIPE_SHADER_GEOMETRY)
return;
assert(binary->bo->ptr.cpu != NULL && "shaders must be CPU mapped");
cache_key cache_key;
@@ -100,6 +106,10 @@ agx_disk_cache_retrieve(struct agx_screen *screen,
if (!cache)
return NULL;
/* TODO: Support caching GS */
if (uncompiled->type == PIPE_SHADER_GEOMETRY)
return NULL;
cache_key cache_key;
agx_disk_cache_compute_key(cache, uncompiled, key, cache_key);
@@ -153,18 +153,14 @@ lower_intrinsic(nir_builder *b, nir_intrinsic_instr *intr)
intr->src[0].ssa);
case nir_intrinsic_load_num_workgroups:
return load_sysval(b, 3, 32, AGX_SYSVAL_TABLE_GRID, 0);
case nir_intrinsic_load_first_vertex:
return load_sysval(b, 1, 32, AGX_SYSVAL_TABLE_PARAMS, 0);
case nir_intrinsic_load_base_instance:
return load_sysval(b, 1, 32, AGX_SYSVAL_TABLE_PARAMS, 4);
case nir_intrinsic_load_layer_id_written_agx:
return load_sysval_root(b, 1, 16, &u->layer_id_written);
case nir_intrinsic_load_xfb_address:
return load_sysval_root(b, 1, 64, &u->xfb.base[nir_intrinsic_base(intr)]);
case nir_intrinsic_load_xfb_size:
return load_sysval_root(b, 1, 32, &u->xfb.size[nir_intrinsic_base(intr)]);
case nir_intrinsic_load_xfb_index_buffer:
return load_sysval_root(b, 1, 64, &u->xfb.index_buffer);
case nir_intrinsic_load_base_vertex:
return load_sysval_root(b, 1, 32, &u->xfb.base_vertex);
case nir_intrinsic_load_num_vertices:
return load_sysval_root(b, 1, 32, &u->xfb.num_vertices);
case nir_intrinsic_load_geometry_param_buffer_agx:
return load_sysval_root(b, 1, 64, &u->geometry_params);
default:
return NULL;
}
File diff suppressed because it is too large Load Diff
+60 -40
View File
@@ -15,6 +15,7 @@
#include "asahi/lib/agx_pack.h"
#include "asahi/lib/agx_tilebuffer.h"
#include "asahi/lib/pool.h"
#include "asahi/lib/shaders/geometry.h"
#include "compiler/nir/nir_lower_blend.h"
#include "compiler/shader_enums.h"
#include "gallium/auxiliary/util/u_blitter.h"
@@ -39,7 +40,7 @@
struct agx_streamout_target {
struct pipe_stream_output_target base;
uint32_t offset;
struct pipe_resource *offset;
};
static inline struct agx_streamout_target *
@@ -48,37 +49,9 @@ agx_so_target(struct pipe_stream_output_target *target)
return (struct agx_streamout_target *)target;
}
struct agx_xfb_key {
/* If true, compiles a "transform feedback" program instead of a vertex
* shader. This is a kernel that runs on the VDM and writes out the transform
* feedback buffers, with no rasterization.
*/
bool active;
/* The index size (1, 2, 4) or 0 if drawing without an index buffer. */
uint8_t index_size;
/* The primitive mode for unrolling the vertex ID */
enum mesa_prim mode;
/* Use first vertex as the provoking vertex for flat shading */
bool flatshade_first;
};
struct agx_xfb_params {
uint64_t base[PIPE_MAX_SO_BUFFERS];
uint32_t size[PIPE_MAX_SO_BUFFERS];
uint64_t index_buffer;
uint32_t base_vertex;
uint32_t num_vertices;
};
struct agx_streamout {
struct pipe_stream_output_target *targets[PIPE_MAX_SO_BUFFERS];
unsigned num_targets;
struct agx_xfb_key key;
struct agx_xfb_params params;
};
/* Shaders can access fixed-function state through system values.
@@ -92,6 +65,7 @@ struct agx_streamout {
*/
enum agx_sysval_table {
AGX_SYSVAL_TABLE_ROOT,
AGX_SYSVAL_TABLE_PARAMS,
AGX_SYSVAL_TABLE_GRID,
AGX_SYSVAL_TABLE_VS,
AGX_SYSVAL_TABLE_TCS,
@@ -125,8 +99,8 @@ struct PACKED agx_draw_uniforms {
/* Vertex buffer object bases, if present */
uint64_t vbo_base[PIPE_MAX_ATTRIBS];
/* Transform feedback info for a transform feedback shader */
struct agx_xfb_params xfb;
/* Address of geometry param buffer if geometry shaders are used, else 0 */
uint64_t geometry_params;
/* Blend constant if any */
float blend_constant[4];
@@ -195,15 +169,27 @@ struct agx_compiled_shader {
/* Uniforms the driver must push */
unsigned push_range_count;
struct agx_push_range push[AGX_MAX_PUSH_RANGES];
/* Auxiliary programs, or NULL if not used */
struct agx_compiled_shader *gs_count, *pre_gs;
struct agx_uncompiled_shader *gs_copy;
/* Output primitive mode for geometry shaders */
enum mesa_prim gs_output_mode;
/* Number of words per primitive in the count buffer */
unsigned gs_count_words;
};
struct agx_uncompiled_shader {
struct pipe_shader_state base;
enum pipe_shader_type type;
struct blob early_serialized_nir;
struct blob serialized_nir;
uint8_t nir_sha1[20];
struct agx_uncompiled_shader_info info;
struct hash_table *variants;
struct agx_uncompiled_shader *passthrough_progs[MESA_PRIM_COUNT];
bool has_xfb_info;
/* Whether the shader accesses indexed samplers via the bindless heap */
@@ -293,6 +279,13 @@ struct agx_batch {
struct agx_draw_uniforms uniforms;
/* Indirect buffer allocated for geometry shader */
uint64_t geom_indirect;
struct agx_bo *geom_indirect_bo;
/* Geometry state buffer if geometry/etc shaders are used */
uint64_t geometry_state;
/* Uploaded descriptors */
uint64_t textures[PIPE_SHADER_TYPES];
uint32_t texture_count[PIPE_SHADER_TYPES];
@@ -333,6 +326,9 @@ struct agx_batch {
/* Result buffer where the kernel places command execution information */
union agx_batch_result *result;
size_t result_off;
/* Actual pointer in a uniform */
struct agx_bo *geom_params_bo;
};
struct agx_zsa {
@@ -357,7 +353,6 @@ struct agx_blend {
struct asahi_vs_shader_key {
struct agx_vbufs vbuf;
struct agx_xfb_key xfb;
uint64_t outputs_flat_shaded;
uint64_t outputs_linear_shaded;
};
@@ -381,8 +376,26 @@ struct asahi_fs_shader_key {
enum pipe_format rt_formats[PIPE_MAX_COLOR_BUFS];
};
struct asahi_gs_shader_key {
/* Input assembly key */
struct agx_ia_key ia;
/* Vertex shader key */
struct agx_vbufs vbuf;
/* If true, this GS is run only for its side effects (including XFB) */
bool rasterizer_discard;
/* Geometry shaders must be linked with a vertex shader. In a monolithic
* pipeline, this is the vertex shader (or tessellation evaluation shader).
* With separate shaders, this needs to be an internal passthrough program.
*/
uint8_t input_nir_sha1[20];
};
union asahi_shader_key {
struct asahi_vs_shader_key vs;
struct asahi_gs_shader_key gs;
struct asahi_fs_shader_key fs;
};
@@ -419,9 +432,12 @@ enum agx_dirty {
struct agx_context {
struct pipe_context base;
struct agx_compiled_shader *vs, *fs;
struct agx_compiled_shader *vs, *fs, *gs;
uint32_t dirty;
/* Heap for dynamic memory allocation for geometry/tessellation shaders */
struct pipe_resource *heap;
/* Acts as a context-level shader key */
bool support_lod_bias;
@@ -480,6 +496,8 @@ struct agx_context {
/* Bound CL global buffers */
struct util_dynarray global_buffers;
struct agx_compiled_shader *gs_prefix_sums[16];
struct agx_compiled_shader *gs_setup_indirect[MESA_PRIM_MAX];
struct agx_meta_cache meta;
uint32_t syncobj;
@@ -541,7 +559,7 @@ agx_context(struct pipe_context *pctx)
}
void agx_launch(struct agx_batch *batch, const struct pipe_grid_info *info,
struct agx_compiled_shader *cs);
struct agx_compiled_shader *cs, enum pipe_shader_type stage);
void agx_init_query_functions(struct pipe_context *ctx);
@@ -550,17 +568,11 @@ agx_primitives_update_direct(struct agx_context *ctx,
const struct pipe_draw_info *info,
const struct pipe_draw_start_count_bias *draw);
void agx_nir_lower_xfb(nir_shader *shader, struct agx_xfb_key *key);
void agx_draw_vbo_from_xfb(struct pipe_context *pctx,
const struct pipe_draw_info *info,
unsigned drawid_offset,
const struct pipe_draw_indirect_info *indirect);
void agx_launch_so(struct pipe_context *pctx, const struct pipe_draw_info *info,
const struct pipe_draw_start_count_bias *draws,
uint64_t index_buffer);
uint64_t agx_batch_get_so_address(struct agx_batch *batch, unsigned buffer,
uint32_t *size);
@@ -770,6 +782,14 @@ bool agx_nir_layout_uniforms(nir_shader *shader,
bool agx_nir_lower_bindings(nir_shader *shader, bool *uses_bindless_samplers);
void agx_nir_lower_gs(nir_shader *gs, nir_shader *input_shader,
const nir_shader *libagx, struct agx_ia_key *ia,
bool rasterizer_discard, nir_shader **gs_count,
nir_shader **gs_copy, nir_shader **pre_gs,
enum mesa_prim *out_mode, unsigned *out_count_words);
nir_shader *agx_nir_prefix_sum_gs(const nir_shader *libagx, unsigned words);
bool agx_batch_is_active(struct agx_batch *batch);
bool agx_batch_is_submitted(struct agx_batch *batch);
+32 -419
View File
@@ -6,8 +6,10 @@
#include "compiler/nir/nir_builder.h"
#include "compiler/nir/nir_xfb_info.h"
#include "pipe/p_defines.h"
#include "util/u_draw.h"
#include "util/u_dump.h"
#include "util/u_inlines.h"
#include "util/u_prim.h"
#include "agx_state.h"
@@ -16,21 +18,24 @@ agx_create_stream_output_target(struct pipe_context *pctx,
struct pipe_resource *prsc,
unsigned buffer_offset, unsigned buffer_size)
{
struct pipe_stream_output_target *target;
target = &rzalloc(pctx, struct agx_streamout_target)->base;
struct agx_streamout_target *target =
rzalloc(pctx, struct agx_streamout_target);
if (!target)
return NULL;
pipe_reference_init(&target->reference, 1);
pipe_resource_reference(&target->buffer, prsc);
pipe_reference_init(&target->base.reference, 1);
pipe_resource_reference(&target->base.buffer, prsc);
target->context = pctx;
target->buffer_offset = buffer_offset;
target->buffer_size = buffer_size;
target->base.context = pctx;
target->base.buffer_offset = buffer_offset;
target->base.buffer_size = buffer_size;
return target;
uint32_t zero = 0;
target->offset = pipe_buffer_create_with_data(pctx, PIPE_BIND_GLOBAL,
PIPE_USAGE_DEFAULT, 4, &zero);
return &target->base;
}
static void
@@ -62,8 +67,10 @@ agx_set_stream_output_targets(struct pipe_context *pctx, unsigned num_targets,
* Gallium contract and it will work out fine. Probably should be
* redefined to be ~0 instead of -1 but it doesn't really matter.
*/
if (offsets[i] != -1)
agx_so_target(targets[i])->offset = offsets[i];
if (offsets[i] != -1) {
pipe_buffer_write(pctx, agx_so_target(targets[i])->offset, 0, 4,
&offsets[i]);
}
pipe_so_target_reference(&so->targets[i], targets[i]);
}
@@ -100,18 +107,11 @@ agx_batch_get_so_address(struct agx_batch *batch, unsigned buffer,
}
/* Otherwise, write the target */
struct pipe_stream_output_info *so =
&batch->ctx->stage[PIPE_SHADER_VERTEX].shader->base.stream_output;
struct agx_resource *rsrc = agx_resource(target->buffer);
agx_batch_writes(batch, rsrc);
/* The amount of space left depends how much we've already consumed */
unsigned stride = so->stride[buffer] * 4;
uint32_t offset = agx_so_target(target)->offset * stride;
*size = offset < target->buffer_size ? (target->buffer_size - offset) : 0;
return rsrc->bo->ptr.gpu + target->buffer_offset + offset;
*size = target->buffer_size;
return rsrc->bo->ptr.gpu + target->buffer_offset;
}
void
@@ -119,9 +119,18 @@ agx_draw_vbo_from_xfb(struct pipe_context *pctx,
const struct pipe_draw_info *info, unsigned drawid_offset,
const struct pipe_draw_indirect_info *indirect)
{
perf_debug_ctx(agx_context(pctx), "draw auto");
unsigned count;
pipe_buffer_read(pctx,
agx_so_target(indirect->count_from_stream_output)->offset,
0, 4, &count);
/* XXX: Probably need to divide here */
struct pipe_draw_start_count_bias draw = {
.start = 0,
.count = agx_so_target(indirect->count_from_stream_output)->offset,
.count = count,
};
pctx->draw_vbo(pctx, info, drawid_offset, NULL, &draw, 1);
@@ -142,114 +151,6 @@ xfb_prims_for_vertices(enum mesa_prim mode, unsigned verts)
return prims;
}
/*
* Launch a streamout pipeline.
*/
void
agx_launch_so(struct pipe_context *pctx, const struct pipe_draw_info *info,
const struct pipe_draw_start_count_bias *draw,
uint64_t index_buffer)
{
struct agx_context *ctx = agx_context(pctx);
/* Break recursion from draw_vbo creating draw calls below: Do not do a
* streamout draw for a streamout draw.
*/
if (ctx->streamout.key.active)
return;
/* Configure the below draw to launch streamout rather than a regular draw */
ctx->streamout.key.active = true;
ctx->dirty |= AGX_DIRTY_XFB;
ctx->streamout.key.index_size = info->index_size;
ctx->streamout.key.mode = info->mode;
ctx->streamout.key.flatshade_first = ctx->rast->base.flatshade_first;
ctx->streamout.params.index_buffer = index_buffer;
/* Ignore provoking vertex for modes that don't depend on the provoking
* vertex, to reduce shader variants.
*/
if (info->mode != MESA_PRIM_TRIANGLE_STRIP)
ctx->streamout.key.flatshade_first = false;
/* Determine how many vertices are XFB there will be */
unsigned num_outputs =
u_stream_outputs_for_vertices(info->mode, draw->count);
unsigned count = draw->count;
u_trim_pipe_prim(info->mode, &count);
ctx->streamout.params.base_vertex =
info->index_size ? draw->index_bias : draw->start;
ctx->streamout.params.num_vertices = count;
/* Streamout runs as a vertex shader with rasterizer discard */
void *saved_rast = ctx->rast;
pctx->bind_rasterizer_state(
pctx, util_blitter_get_discard_rasterizer_state(ctx->blitter));
/* Dispatch a grid of points, this is compute-like */
util_draw_arrays_instanced(pctx, MESA_PRIM_POINTS, 0, num_outputs, 0,
info->instance_count);
pctx->bind_rasterizer_state(pctx, saved_rast);
/*
* Finally, if needed, update the counter of primitives written. The spec
* requires:
*
* If recording the vertices of a primitive to the buffer objects being
* used for transform feedback purposes would result in [overflow]...
* the counter corresponding to the asynchronous query target
* TRANSFORM_FEEDBACK_PRIMITIVES_WRITTEN (see section 13.4) is not
* incremented.
*
* So clamp the number of primitives generated to the number of primitives
* we actually have space to write.
*/
if (ctx->tf_prims_generated) {
uint32_t min_max = ~0;
for (unsigned i = 0; i < ctx->streamout.num_targets; ++i) {
struct pipe_stream_output_target *target = get_target(ctx, i);
if (!target)
continue;
struct pipe_stream_output_info *so =
&ctx->stage[PIPE_SHADER_VERTEX].shader->base.stream_output;
unsigned stride = so->stride[i] * 4;
/* Ignore spurious targets. I don't see anything in the Gallium
* contract specifically forbidding this.
*/
if (stride == 0)
continue;
uint32_t offset = agx_so_target(target)->offset * stride;
uint32_t remaining =
offset < target->buffer_size ? (target->buffer_size - offset) : 0;
uint32_t max_vertices = stride ? (remaining / stride) : ~0;
min_max = MIN2(min_max, max_vertices);
}
/* We now have the maximum vertices written, round down to primitives */
uint32_t max_prims = xfb_prims_for_vertices(info->mode, min_max);
uint32_t prims = xfb_prims_for_vertices(info->mode, draw->count);
ctx->tf_prims_generated->value += MIN2(prims, max_prims);
}
/* Update the offsets into the streamout buffers */
for (unsigned i = 0; i < ctx->streamout.num_targets; ++i) {
if (ctx->streamout.targets[i])
agx_so_target(ctx->streamout.targets[i])->offset += num_outputs;
}
ctx->dirty |= AGX_DIRTY_XFB;
ctx->streamout.key.active = false;
}
/*
* Count generated primitives on the CPU for transform feedback. This only works
* in the absence of indirect draws, geometry shaders, or tessellation.
@@ -260,301 +161,13 @@ agx_primitives_update_direct(struct agx_context *ctx,
const struct pipe_draw_start_count_bias *draw)
{
assert(ctx->active_queries && ctx->prims_generated && "precondition");
assert(!ctx->stage[PIPE_SHADER_GEOMETRY].shader &&
"Geometry shaders use their own counting");
ctx->prims_generated->value +=
xfb_prims_for_vertices(info->mode, draw->count);
}
/* The OpenGL spec says:
*
* If recording the vertices of a primitive to the buffer objects being
* used for transform feedback purposes would result in either exceeding
* the limits of any buffer objects size, or in exceeding the end
* position offset + size 1, as set by BindBufferRange, then no vertices
* of that primitive are recorded in any buffer object.
*
* This function checks for the absence of overflow.
*
* The difficulty is that we are processing a single vertex at a time, so we
* need to do some arithmetic to figure out the bounds for the whole containing
* primitive.
*
* XXX: How do quads get tessellated?
*/
static nir_def *
primitive_fits(nir_builder *b, struct agx_xfb_key *key)
{
/* Get the number of vertices per primitive in the current mode, usually just
* the base number but quads are tessellated.
*/
uint32_t verts_per_prim = mesa_vertices_per_prim(key->mode);
if (u_decomposed_prim(key->mode) == MESA_PRIM_QUADS)
verts_per_prim = 6;
/* Get the ID for this invocation */
nir_def *id = nir_load_vertex_id_zero_base(b);
/* Figure out the ID for the first vertex of the next primitive. Since
* transform feedback buffers are tightly packed, that's one byte after the
* end of this primitive, which will make bounds checking convenient. That
* will be:
*
* (id - (id % prim size)) + prim size
*/
nir_def *rem = nir_umod_imm(b, id, verts_per_prim);
nir_def *next_id = nir_iadd_imm(b, nir_isub(b, id, rem), verts_per_prim);
/* Figure out where that vertex will land */
nir_def *index = nir_iadd(
b, nir_imul(b, nir_load_instance_id(b), nir_load_num_vertices(b)),
next_id);
/* Now check for overflow in each written buffer */
nir_def *all_fits = nir_imm_true(b);
u_foreach_bit(buffer, b->shader->xfb_info->buffers_written) {
uint16_t stride = b->shader->info.xfb_stride[buffer] * 4;
assert(stride != 0);
/* For this primitive to fit, the next primitive cannot start after the
* end of the transform feedback buffer.
*/
nir_def *end_offset = nir_imul_imm(b, index, stride);
/* Check whether that will remain in bounds */
nir_def *fits =
nir_uge(b, nir_load_xfb_size(b, .base = buffer), end_offset);
/* Accumulate */
all_fits = nir_iand(b, all_fits, fits);
}
return all_fits;
}
static void
insert_overflow_check(nir_shader *nir, struct agx_xfb_key *key)
{
nir_function_impl *impl = nir_shader_get_entrypoint(nir);
/* Extract the current transform feedback shader */
nir_cf_list list;
nir_cf_extract(&list, nir_before_impl(impl), nir_after_impl(impl));
/* Get a builder for the (now empty) shader */
nir_builder b = nir_builder_at(nir_after_block(nir_start_block(impl)));
/* Rebuild the shader as
*
* if (!overflow) {
* shader();
* }
*/
nir_push_if(&b, primitive_fits(&b, key));
{
b.cursor = nir_cf_reinsert(&list, b.cursor);
}
nir_pop_if(&b, NULL);
}
static void
lower_xfb_output(nir_builder *b, nir_intrinsic_instr *intr,
unsigned start_component, unsigned num_components,
unsigned buffer, unsigned offset_words)
{
assert(buffer < MAX_XFB_BUFFERS);
assert(nir_intrinsic_component(intr) == 0); // TODO
/* Transform feedback info in units of words, convert to bytes. */
uint16_t stride = b->shader->info.xfb_stride[buffer] * 4;
assert(stride != 0);
uint16_t offset = offset_words * 4;
nir_def *index = nir_iadd(
b, nir_imul(b, nir_load_instance_id(b), nir_load_num_vertices(b)),
nir_load_vertex_id_zero_base(b));
nir_def *xfb_offset =
nir_iadd_imm(b, nir_imul_imm(b, index, stride), offset);
nir_def *buf = nir_load_xfb_address(b, 64, .base = buffer);
nir_def *addr = nir_iadd(b, buf, nir_u2u64(b, xfb_offset));
nir_def *value = nir_channels(
b, intr->src[0].ssa, BITFIELD_MASK(num_components) << start_component);
nir_store_global(b, addr, 4, value, nir_component_mask(num_components));
}
static bool
lower_xfb(nir_builder *b, nir_intrinsic_instr *intr, UNUSED void *data)
{
if (intr->intrinsic != nir_intrinsic_store_output)
return false;
/* Assume the inputs are read */
BITSET_SET(b->shader->info.system_values_read,
SYSTEM_VALUE_VERTEX_ID_ZERO_BASE);
BITSET_SET(b->shader->info.system_values_read, SYSTEM_VALUE_INSTANCE_ID);
bool progress = false;
for (unsigned i = 0; i < 2; ++i) {
nir_io_xfb xfb =
i ? nir_intrinsic_io_xfb2(intr) : nir_intrinsic_io_xfb(intr);
for (unsigned j = 0; j < 2; ++j) {
if (xfb.out[j].num_components > 0) {
b->cursor = nir_before_instr(&intr->instr);
lower_xfb_output(b, intr, i * 2 + j, xfb.out[j].num_components,
xfb.out[j].buffer, xfb.out[j].offset);
progress = true;
}
}
}
nir_instr_remove(&intr->instr);
return progress;
}
static bool
lower_xfb_intrinsics(struct nir_builder *b, nir_intrinsic_instr *intr,
void *data)
{
b->cursor = nir_before_instr(&intr->instr);
struct agx_xfb_key *key = data;
switch (intr->intrinsic) {
/* XXX: Rename to "xfb index" to avoid the clash */
case nir_intrinsic_load_vertex_id_zero_base: {
nir_def *id = nir_load_vertex_id(b);
nir_def_rewrite_uses(&intr->def, id);
return true;
}
case nir_intrinsic_load_vertex_id: {
/* Get the raw invocation ID */
nir_def *id = nir_load_vertex_id(b);
/* Tessellate by primitive mode */
if (key->mode == MESA_PRIM_LINE_STRIP ||
key->mode == MESA_PRIM_LINE_LOOP) {
/* The last vertex is special for a loop. Check if that's we're dealing
* with.
*/
nir_def *num_invocations =
nir_imul_imm(b, nir_load_num_vertices(b), 2);
nir_def *last_vertex =
nir_ieq(b, id, nir_iadd_imm(b, num_invocations, -1));
/* (0, 1), (1, 2) */
id = nir_iadd(b, nir_ushr_imm(b, id, 1), nir_iand_imm(b, id, 1));
/* (0, 1), (1, 2), (2, 0) */
if (key->mode == MESA_PRIM_LINE_LOOP) {
id = nir_bcsel(b, last_vertex, nir_imm_int(b, 0), id);
}
} else if (key->mode == MESA_PRIM_TRIANGLE_STRIP) {
/* Order depends on the provoking vertex.
*
* First: (0, 1, 2), (1, 3, 2), (2, 3, 4).
* Last: (0, 1, 2), (2, 1, 3), (2, 3, 4).
*/
nir_def *prim = nir_udiv_imm(b, id, 3);
nir_def *rem = nir_umod_imm(b, id, 3);
unsigned pv = key->flatshade_first ? 0 : 2;
/* Swap the two non-provoking vertices third vertex in odd triangles */
nir_def *even = nir_ieq_imm(b, nir_iand_imm(b, prim, 1), 0);
nir_def *is_provoking = nir_ieq_imm(b, rem, pv);
nir_def *no_swap = nir_ior(b, is_provoking, even);
nir_def *swapped = nir_isub_imm(b, 3 - pv, rem);
nir_def *off = nir_bcsel(b, no_swap, rem, swapped);
/* Pull the (maybe swapped) vertex from the corresponding primitive */
id = nir_iadd(b, prim, off);
} else if (key->mode == MESA_PRIM_TRIANGLE_FAN) {
/* (0, 1, 2), (0, 2, 3) */
nir_def *prim = nir_udiv_imm(b, id, 3);
nir_def *rem = nir_umod_imm(b, id, 3);
id = nir_bcsel(b, nir_ieq_imm(b, rem, 0), nir_imm_int(b, 0),
nir_iadd(b, prim, rem));
} else if (key->mode == MESA_PRIM_QUADS ||
key->mode == MESA_PRIM_QUAD_STRIP) {
/* Quads: [(0, 1, 3), (3, 1, 2)], [(4, 5, 7), (7, 5, 6)]
* Quad strips: [(0, 1, 3), (0, 2, 3)], [(2, 3, 5), (2, 4, 5)]
*/
bool strips = key->mode == MESA_PRIM_QUAD_STRIP;
nir_def *prim = nir_udiv_imm(b, id, 6);
nir_def *rem = nir_umod_imm(b, id, 6);
nir_def *base = nir_imul_imm(b, prim, strips ? 2 : 4);
/* Quads: [0, 1, 3, 3, 1, 2]
* Quad strips: [0, 1, 3, 0, 2, 3]
*/
uint32_t order_quads = 0x213310;
uint32_t order_strips = 0x230310;
uint32_t order = strips ? order_strips : order_quads;
/* Index out of the bitpacked array */
nir_def *offset = nir_iand_imm(
b, nir_ushr(b, nir_imm_int(b, order), nir_imul_imm(b, rem, 4)),
0xF);
id = nir_iadd(b, base, offset);
}
/* Add the "start", either an index bias or a base vertex */
id = nir_iadd(b, id, nir_load_base_vertex(b));
/* If drawing with an index buffer, pull the vertex ID. Otherwise, the
* vertex ID is just the index as-is.
*/
if (key->index_size) {
nir_def *index_buffer = nir_load_xfb_index_buffer(b, 64);
nir_def *offset = nir_imul_imm(b, id, key->index_size);
nir_def *address = nir_iadd(b, index_buffer, nir_u2u64(b, offset));
nir_def *index = nir_load_global_constant(b, address, key->index_size,
1, key->index_size * 8);
id = nir_u2uN(b, index, id->bit_size);
}
nir_def_rewrite_uses(&intr->def, id);
return true;
}
default:
return false;
}
}
void
agx_nir_lower_xfb(nir_shader *nir, struct agx_xfb_key *key)
{
assert(nir->info.stage == MESA_SHADER_VERTEX);
NIR_PASS_V(nir, nir_io_add_const_offset_to_base,
nir_var_shader_in | nir_var_shader_out);
NIR_PASS_V(nir, nir_io_add_intrinsic_xfb_info);
NIR_PASS_V(nir, insert_overflow_check, key);
NIR_PASS_V(nir, nir_shader_intrinsics_pass, lower_xfb,
nir_metadata_block_index | nir_metadata_dominance, key);
NIR_PASS_V(nir, nir_shader_intrinsics_pass, lower_xfb_intrinsics,
nir_metadata_block_index | nir_metadata_dominance, key);
/* Lowering XFB creates piles of dead code. Eliminate now so we don't
* push unnecessary sysvals.
*/
NIR_PASS_V(nir, nir_opt_dce);
}
void
agx_init_streamout_functions(struct pipe_context *ctx)
{
-11
View File
@@ -69,17 +69,6 @@ agx_upload_uniforms(struct agx_batch *batch)
batch->uniforms.tables[AGX_SYSVAL_TABLE_ROOT] = root_ptr.gpu;
batch->uniforms.sample_mask = ctx->sample_mask;
if (ctx->streamout.key.active) {
batch->uniforms.xfb = ctx->streamout.params;
for (unsigned i = 0; i < batch->ctx->streamout.num_targets; ++i) {
uint32_t size = 0;
batch->uniforms.xfb.base[i] =
agx_batch_get_so_address(batch, i, &size);
batch->uniforms.xfb.size[i] = size;
}
}
memcpy(root_ptr.cpu, &batch->uniforms, sizeof(batch->uniforms));
}