blob: 1798f9e1dee922b2f4a6db7428ec4a5403d2657c [file]
/*
* Copyright 2023 Alyssa Rosenzweig
* Copyright 2023 Valve Corporation
* SPDX-License-Identifier: MIT
*/
#include "asahi/lib/agx_abi.h"
#include "compiler/libcl/libcl_vk.h"
#include "util/macros.h"
#include "util/u_math.h"
#include "geometry.h"
#include "libagx_intrinsics.h"
#include "query.h"
#include "tessellator.h"
/* Swap the two non-provoking vertices in odd triangles. This generates a vertex
* ID list with a consistent winding order.
*
* Holding prim and flatshade_first constant, the map : [0, 1, 2] -> [0, 1, 2]
* is its own inverse. It is hence used both vertex fetch and transform
* feedback.
*/
uint
libagx_map_vertex_in_tri_strip(uint prim, uint vert, bool flatshade_first)
{
unsigned pv = flatshade_first ? 0 : 2;
bool even = (prim & 1) == 0;
bool provoking = vert == pv;
return (provoking || even) ? vert : ((3 - pv) - vert);
}
static inline uint
xfb_prim(uint id, uint n, uint copy)
{
return sub_sat(id, n - 1u) + copy;
}
/*
* Determine whether an output vertex has an n'th copy in the transform feedback
* buffer. This is written weirdly to let constant folding remove unnecessary
* stores when length is known statically.
*/
bool
libagx_xfb_vertex_copy_in_strip(uint n, uint id, uint length, uint copy)
{
uint prim = xfb_prim(id, n, copy);
int num_prims = length - (n - 1);
return copy == 0 || (prim < num_prims && id >= copy && copy < num_prims);
}
uint
libagx_xfb_vertex_offset(uint n, uint invocation_base_prim,
uint strip_base_prim, uint id_in_strip, uint copy,
bool flatshade_first)
{
uint prim = xfb_prim(id_in_strip, n, copy);
uint vert_0 = min(id_in_strip, n - 1);
uint vert = vert_0 - copy;
if (n == 3) {
vert = libagx_map_vertex_in_tri_strip(prim, vert, flatshade_first);
}
/* Tally up in the whole buffer */
uint base_prim = invocation_base_prim + strip_base_prim;
uint base_vertex = base_prim * n;
return base_vertex + (prim * n) + vert;
}
uint64_t
libagx_xfb_vertex_address(constant struct agx_geometry_params *p, uint index,
uint buffer, uint stride, uint output_offset)
{
uint xfb_offset = (index * stride) + output_offset;
return (uintptr_t)(p->xfb_base[buffer]) + xfb_offset;
}
uint
libagx_vertex_id_for_line_loop(uint prim, uint vert, uint num_prims)
{
/* (0, 1), (1, 2), (2, 0) */
if (prim == (num_prims - 1) && vert == 1)
return 0;
else
return prim + vert;
}
uint
libagx_vertex_id_for_line_class(enum mesa_prim mode, uint prim, uint vert,
uint num_prims)
{
/* Line list, line strip, or line loop */
if (mode == MESA_PRIM_LINE_LOOP && prim == (num_prims - 1) && vert == 1)
return 0;
if (mode == MESA_PRIM_LINES)
prim *= 2;
return prim + vert;
}
uint
libagx_vertex_id_for_tri_fan(uint prim, uint vert, bool flatshade_first)
{
/* Vulkan spec section 20.1.7 gives (i + 1, i + 2, 0) for a provoking
* first. OpenGL instead wants (0, i + 1, i + 2) with a provoking last.
* Piglit clipflat expects us to switch between these orders depending on
* provoking vertex, to avoid trivializing the fan.
*
* Rotate accordingly.
*/
if (flatshade_first) {
vert = (vert == 2) ? 0 : (vert + 1);
}
/* The simpler form assuming last is provoking. */
return (vert == 0) ? 0 : prim + vert;
}
uint
libagx_vertex_id_for_tri_class(enum mesa_prim mode, uint prim, uint vert,
bool flatshade_first)
{
if (flatshade_first && mode == MESA_PRIM_TRIANGLE_FAN) {
vert = vert + 1;
vert = (vert == 3) ? 0 : vert;
}
if (mode == MESA_PRIM_TRIANGLE_FAN && vert == 0)
return 0;
if (mode == MESA_PRIM_TRIANGLES)
prim *= 3;
/* Triangle list, triangle strip, or triangle fan */
if (mode == MESA_PRIM_TRIANGLE_STRIP) {
unsigned pv = flatshade_first ? 0 : 2;
bool even = (prim & 1) == 0;
bool provoking = vert == pv;
vert = ((provoking || even) ? vert : ((3 - pv) - vert));
}
return prim + vert;
}
uint
libagx_vertex_id_for_line_adj_class(enum mesa_prim mode, uint prim, uint vert)
{
/* Line list adj or line strip adj */
if (mode == MESA_PRIM_LINES_ADJACENCY)
prim *= 4;
return prim + vert;
}
uint
libagx_vertex_id_for_tri_strip_adj(uint prim, uint vert, uint num_prims,
bool flatshade_first)
{
/* See Vulkan spec section 20.1.11 "Triangle Strips With Adjancency".
*
* There are different cases for first/middle/last/only primitives and for
* odd/even primitives. Determine which case we're in.
*/
bool last = prim == (num_prims - 1);
bool first = prim == 0;
bool even = (prim & 1) == 0;
bool even_or_first = even || first;
/* When the last vertex is provoking, we rotate the primitives
* accordingly. This seems required for OpenGL.
*/
if (!flatshade_first && !even_or_first) {
vert = (vert + 4u) % 6u;
}
/* Offsets per the spec. The spec lists 6 cases with 6 offsets. Luckily,
* there are lots of patterns we can exploit, avoiding a full 6x6 LUT.
*
* Here we assume the first vertex is provoking, the Vulkan default.
*/
uint offsets[6] = {
0,
first ? 1 : (even ? -2 : 3),
even_or_first ? 2 : 4,
last ? 5 : 6,
even_or_first ? 4 : 2,
even_or_first ? 3 : -2,
};
/* Ensure NIR can see thru the local array */
uint offset = 0;
for (uint i = 1; i < 6; ++i) {
if (i == vert)
offset = offsets[i];
}
/* Finally add to the base of the primitive */
return (prim * 2) + offset;
}
uint
libagx_vertex_id_for_tri_adj_class(enum mesa_prim mode, uint prim, uint vert,
uint nr, bool flatshade_first)
{
/* Tri adj list or tri adj strip */
if (mode == MESA_PRIM_TRIANGLE_STRIP_ADJACENCY) {
return libagx_vertex_id_for_tri_strip_adj(prim, vert, nr,
flatshade_first);
} else {
return (6 * prim) + vert;
}
}
static uint
vertex_id_for_topology(enum mesa_prim mode, bool flatshade_first, uint prim,
uint vert, uint num_prims)
{
switch (mode) {
case MESA_PRIM_POINTS:
case MESA_PRIM_LINES:
case MESA_PRIM_TRIANGLES:
case MESA_PRIM_LINES_ADJACENCY:
case MESA_PRIM_TRIANGLES_ADJACENCY:
/* Regular primitive: every N vertices defines a primitive */
return (prim * mesa_vertices_per_prim(mode)) + vert;
case MESA_PRIM_LINE_LOOP:
return libagx_vertex_id_for_line_loop(prim, vert, num_prims);
case MESA_PRIM_LINE_STRIP:
case MESA_PRIM_LINE_STRIP_ADJACENCY:
/* (i, i + 1) or (i, ..., i + 3) */
return prim + vert;
case MESA_PRIM_TRIANGLE_STRIP: {
/* Order depends on the provoking vert.
*
* First: (0, 1, 2), (1, 3, 2), (2, 3, 4).
* Last: (0, 1, 2), (2, 1, 3), (2, 3, 4).
*
* Pull the (maybe swapped) vert from the corresponding primitive
*/
return prim + libagx_map_vertex_in_tri_strip(prim, vert, flatshade_first);
}
case MESA_PRIM_TRIANGLE_FAN:
return libagx_vertex_id_for_tri_fan(prim, vert, flatshade_first);
case MESA_PRIM_TRIANGLE_STRIP_ADJACENCY:
return libagx_vertex_id_for_tri_strip_adj(prim, vert, num_prims,
flatshade_first);
default:
return 0;
}
}
uint
libagx_map_to_line_adj(uint id)
{
/* Sequence (1, 2), (5, 6), (9, 10), ... */
return ((id & ~1) * 2) + (id & 1) + 1;
}
uint
libagx_map_to_line_strip_adj(uint id)
{
/* Sequence (1, 2), (2, 3), (4, 5), .. */
uint prim = id / 2;
uint vert = id & 1;
return prim + vert + 1;
}
uint
libagx_map_to_tri_strip_adj(uint id)
{
/* Sequence (0, 2, 4), (2, 6, 4), (4, 6, 8), (6, 10, 8)
*
* Although tri strips with adjacency have 6 cases in general, after
* disregarding the vertices only available in a geometry shader, there are
* only even/odd cases. In other words, it's just a triangle strip subject to
* extra padding.
*
* Dividing through by two, the sequence is:
*
* (0, 1, 2), (1, 3, 2), (2, 3, 4), (3, 5, 4)
*/
uint prim = id / 3;
uint vtx = id % 3;
/* Flip the winding order of odd triangles */
if ((prim % 2) == 1) {
if (vtx == 1)
vtx = 2;
else if (vtx == 2)
vtx = 1;
}
return 2 * (prim + vtx);
}
static void
store_index(uintptr_t index_buffer, uint index_size_B, uint id, uint value)
{
global uint32_t *out_32 = (global uint32_t *)index_buffer;
global uint16_t *out_16 = (global uint16_t *)index_buffer;
global uint8_t *out_8 = (global uint8_t *)index_buffer;
if (index_size_B == 4)
out_32[id] = value;
else if (index_size_B == 2)
out_16[id] = value;
else
out_8[id] = value;
}
static uint
load_index(uintptr_t index_buffer, uint32_t index_buffer_range_el, uint id,
uint index_size)
{
bool oob = id >= index_buffer_range_el;
/* If the load would be out-of-bounds, load the first element which is
* assumed valid. If the application index buffer is empty with robustness2,
* index_buffer will point to a zero sink where only the first is valid.
*/
if (oob) {
id = 0;
}
uint el;
if (index_size == 1) {
el = ((constant uint8_t *)index_buffer)[id];
} else if (index_size == 2) {
el = ((constant uint16_t *)index_buffer)[id];
} else {
el = ((constant uint32_t *)index_buffer)[id];
}
/* D3D robustness semantics. TODO: Optimize? */
if (oob) {
el = 0;
}
return el;
}
uint
libagx_load_index_buffer(constant struct agx_ia_state *p, uint id,
uint index_size)
{
return load_index(p->index_buffer, p->index_buffer_range_el, id, index_size);
}
static void
increment_counters(global uint32_t *a, global uint32_t *b, global uint32_t *c,
uint count)
{
global uint32_t *ptr[] = {a, b, c};
for (uint i = 0; i < 3; ++i) {
if (ptr[i]) {
*(ptr[i]) += count;
}
}
}
static unsigned
decomposed_prims_for_vertices_with_tess(enum mesa_prim prim, int vertices,
unsigned verts_per_patch)
{
if (prim >= MESA_PRIM_PATCHES) {
return vertices / verts_per_patch;
} else {
return u_decomposed_prims_for_vertices(prim, vertices);
}
}
KERNEL(1)
libagx_increment_ia(global uint32_t *ia_vertices,
global uint32_t *ia_primitives,
global uint32_t *vs_invocations, global uint32_t *c_prims,
global uint32_t *c_invs, constant uint32_t *draw,
enum mesa_prim prim, unsigned verts_per_patch)
{
increment_counters(ia_vertices, vs_invocations, NULL, draw[0] * draw[1]);
uint prims =
decomposed_prims_for_vertices_with_tess(prim, draw[0], verts_per_patch) *
draw[1];
increment_counters(ia_primitives, c_prims, c_invs, prims);
}
KERNEL(1024)
libagx_increment_ia_restart(global uint32_t *ia_vertices,
global uint32_t *ia_primitives,
global uint32_t *vs_invocations,
global uint32_t *c_prims, global uint32_t *c_invs,
constant uint32_t *draw, uint64_t index_buffer,
uint32_t index_buffer_range_el,
uint32_t restart_index, uint32_t index_size_B,
enum mesa_prim prim, unsigned verts_per_patch)
{
uint tid = cl_global_id.x;
unsigned count = draw[0];
local uint scratch;
uint start = draw[2];
uint partial = 0;
/* Count non-restart indices */
for (uint i = tid; i < count; i += 1024) {
uint index = load_index(index_buffer, index_buffer_range_el, start + i,
index_size_B);
if (index != restart_index)
partial++;
}
/* Accumulate the partials across the workgroup */
scratch = 0;
barrier(CLK_LOCAL_MEM_FENCE);
atomic_add(&scratch, partial);
barrier(CLK_LOCAL_MEM_FENCE);
/* Elect a single thread from the workgroup to increment the counters */
if (tid == 0) {
increment_counters(ia_vertices, vs_invocations, NULL, scratch * draw[1]);
}
/* TODO: We should vectorize this */
if ((ia_primitives || c_prims || c_invs) && tid == 0) {
uint accum = 0;
int last_restart = -1;
for (uint i = 0; i < count; ++i) {
uint index = load_index(index_buffer, index_buffer_range_el, start + i,
index_size_B);
if (index == restart_index) {
accum += decomposed_prims_for_vertices_with_tess(
prim, i - last_restart - 1, verts_per_patch);
last_restart = i;
}
}
{
accum += decomposed_prims_for_vertices_with_tess(
prim, count - last_restart - 1, verts_per_patch);
}
increment_counters(ia_primitives, c_prims, c_invs, accum * draw[1]);
}
}
/*
* Return the ID of the first thread in the workgroup where cond is true, or
* 1024 if cond is false across the workgroup.
*/
static uint
first_true_thread_in_workgroup(bool cond, local uint *scratch)
{
barrier(CLK_LOCAL_MEM_FENCE);
scratch[get_sub_group_id()] = sub_group_ballot(cond)[0];
barrier(CLK_LOCAL_MEM_FENCE);
uint first_group =
ctz(sub_group_ballot(scratch[get_sub_group_local_id()])[0]);
uint off = ctz(first_group < 32 ? scratch[first_group] : 0);
return (first_group * 32) + off;
}
/*
* When unrolling the index buffer for a draw, we translate the old indirect
* draws to new indirect draws. This routine allocates the new index buffer and
* sets up most of the new draw descriptor.
*/
static global void *
setup_unroll_for_draw(global struct agx_heap *heap, constant uint *in_draw,
global uint *out, enum mesa_prim mode, uint index_size_B)
{
/* Determine an upper bound on the memory required for the index buffer.
* Restarts only decrease the unrolled index buffer size, so the maximum size
* is the unrolled size when the input has no restarts.
*/
uint max_prims = u_decomposed_prims_for_vertices(mode, in_draw[0]);
uint max_verts = max_prims * mesa_vertices_per_prim(mode);
uint alloc_size = max_verts * index_size_B;
/* Allocate unrolled index buffer.
*
* TODO: For multidraw, should be atomic. But multidraw+unroll isn't
* currently wired up in any driver.
*/
uint old_heap_bottom_B = agx_heap_alloc_nonatomic_offs(heap, alloc_size);
/* Setup most of the descriptor. Count will be determined after unroll. */
out[1] = in_draw[1]; /* instance count */
out[2] = old_heap_bottom_B / index_size_B; /* index offset */
out[3] = in_draw[3]; /* index bias */
out[4] = in_draw[4]; /* base instance */
/* Return the index buffer we allocated */
return (global uchar *)heap->base + old_heap_bottom_B;
}
KERNEL(1024)
libagx_unroll_restart(global struct agx_heap *heap, uint64_t index_buffer,
constant uint *in_draw, global uint32_t *out_draw,
uint32_t max_draws, uint32_t restart_index,
uint32_t index_buffer_size_el, uint32_t index_size_log2,
uint32_t flatshade_first, uint mode__11)
{
uint32_t index_size_B = 1 << index_size_log2;
enum mesa_prim mode = libagx_uncompact_prim(mode__11);
uint tid = cl_local_id.x;
uint count = in_draw[0];
local uintptr_t out_ptr;
if (tid == 0) {
out_ptr = (uintptr_t)setup_unroll_for_draw(heap, in_draw, out_draw, mode,
index_size_B);
}
barrier(CLK_LOCAL_MEM_FENCE);
uintptr_t in_ptr = (uintptr_t)(libagx_index_buffer(
index_buffer, index_buffer_size_el, in_draw[2], index_size_B));
local uint scratch[32];
uint out_prims = 0;
uint needle = 0;
uint per_prim = mesa_vertices_per_prim(mode);
while (needle < count) {
/* Search for next restart or the end. Lanes load in parallel. */
uint next_restart = needle;
for (;;) {
uint idx = next_restart + tid;
bool restart =
idx >= count || load_index(in_ptr, index_buffer_size_el, idx,
index_size_B) == restart_index;
uint next_offs = first_true_thread_in_workgroup(restart, scratch);
next_restart += next_offs;
if (next_offs < 1024)
break;
}
/* Emit up to the next restart. Lanes output in parallel */
uint subcount = next_restart - needle;
uint subprims = u_decomposed_prims_for_vertices(mode, subcount);
uint out_prims_base = out_prims;
for (uint i = tid; i < subprims; i += 1024) {
for (uint vtx = 0; vtx < per_prim; ++vtx) {
uint id =
vertex_id_for_topology(mode, flatshade_first, i, vtx, subprims);
uint offset = needle + id;
uint x = ((out_prims_base + i) * per_prim) + vtx;
uint y =
load_index(in_ptr, index_buffer_size_el, offset, index_size_B);
store_index(out_ptr, index_size_B, x, y);
}
}
out_prims += subprims;
needle = next_restart + 1;
}
if (tid == 0)
out_draw[0] = out_prims * per_prim;
}
uint
libagx_setup_xfb_buffer(global struct agx_geometry_params *p, uint i,
uint stride, uint max_output_end,
uint vertices_per_prim)
{
uint xfb_offset = *(p->xfb_offs_ptrs[i]);
p->xfb_base[i] = p->xfb_base_original[i] + xfb_offset;
/* Let output_end = output_offset + output_size.
*
* Primitive P will write up to (but not including) offset:
*
* xfb_offset + ((P - 1) * (verts_per_prim * stride))
* + ((verts_per_prim - 1) * stride)
* + output_end
*
* To fit all outputs for P, that value must be less than the XFB
* buffer size for the output with maximal output_end, as everything
* else is constant here across outputs within a buffer/primitive:
*
* floor(P) <= (stride + size - xfb_offset - output_end)
* // (stride * verts_per_prim)
*/
int numer_s = p->xfb_size[i] + (stride - max_output_end) - xfb_offset;
uint numer = max(numer_s, 0);
return numer / (stride * vertices_per_prim);
}
void
libagx_write_strip(GLOBAL uint32_t *index_buffer, uint32_t inv_index_offset,
uint32_t prim_index_offset, uint32_t vertex_offset,
uint32_t verts_in_prim, uint3 info)
{
_libagx_write_strip(index_buffer, inv_index_offset + prim_index_offset,
vertex_offset, verts_in_prim, info.x, info.y, info.z);
}
void
libagx_pad_index_gs(global int *index_buffer, uint inv_index_offset,
uint nr_indices, uint alloc)
{
for (uint i = nr_indices; i < alloc; ++i) {
index_buffer[inv_index_offset + i] = -1;
}
}
KERNEL(1)
libagx_gs_setup_indirect(
uint64_t index_buffer, constant uint *draw,
global uintptr_t *vertex_buffer /* output */,
global struct agx_ia_state *ia /* output */,
global struct agx_geometry_params *p /* output */,
global struct agx_heap *heap,
uint64_t vs_outputs /* Vertex (TES) output mask */,
uint32_t index_size_B /* 0 if no index bffer */,
uint32_t index_buffer_range_el,
uint32_t prim /* Input primitive type, enum mesa_prim */,
int is_prefix_summing, uint max_indices, enum agx_gs_shape shape)
{
/* Determine the (primitives, instances) grid size. */
uint vertex_count = draw[0];
uint instance_count = draw[1];
ia->verts_per_instance = vertex_count;
/* Calculate number of primitives input into the GS */
uint prim_per_instance = u_decomposed_prims_for_vertices(prim, vertex_count);
p->input_primitives = prim_per_instance * instance_count;
/* Invoke VS as (vertices, instances); GS as (primitives, instances) */
p->vs_grid[0] = vertex_count;
p->vs_grid[1] = instance_count;
p->gs_grid[0] = prim_per_instance;
p->gs_grid[1] = instance_count;
p->primitives_log2 = util_logbase2_ceil(prim_per_instance);
/* If indexing is enabled, the third word is the offset into the index buffer
* in elements. Apply that offset now that we have it. For a hardware
* indirect draw, the hardware would do this for us, but for software input
* assembly we need to do it ourselves.
*/
if (index_size_B) {
ia->index_buffer = libagx_index_buffer(
index_buffer, index_buffer_range_el, draw[2], index_size_B);
ia->index_buffer_range_el =
libagx_index_buffer_range_el(index_buffer_range_el, draw[2]);
}
/* We need to allocate VS and GS count buffers, do so now */
uint vertex_buffer_size =
libagx_tcs_in_size(vertex_count * instance_count, vs_outputs);
if (is_prefix_summing) {
p->count_buffer = agx_heap_alloc_nonatomic(
heap, p->input_primitives * p->count_buffer_stride);
}
p->input_buffer =
(uintptr_t)agx_heap_alloc_nonatomic(heap, vertex_buffer_size);
*vertex_buffer = p->input_buffer;
p->input_mask = vs_outputs;
/* Allocate the index buffer and write the draw consuming it */
global VkDrawIndexedIndirectCommand *cmd = (global void *)p->indirect_desc;
*cmd = (VkDrawIndexedIndirectCommand){
.indexCount = agx_gs_rast_vertices(shape, max_indices, prim_per_instance,
instance_count),
.instanceCount = agx_gs_rast_instances(shape, max_indices,
prim_per_instance, instance_count),
};
if (shape == AGX_GS_SHAPE_DYNAMIC_INDEXED) {
cmd->firstIndex =
agx_heap_alloc_nonatomic_offs(heap, cmd->indexCount * 4) / 4;
p->output_index_buffer =
(global uint *)(heap->base + (cmd->firstIndex * 4));
}
}
/*
* Returns (work_group_scan_inclusive_add(x), work_group_sum(x)). Implemented
* manually with subgroup ops and local memory since Mesa doesn't do those
* lowerings yet.
*/
static uint2
libagx_work_group_scan_inclusive_add(uint x, local uint *scratch)
{
uint sg_id = get_sub_group_id();
/* Partial prefix sum of the subgroup */
uint sg = sub_group_scan_inclusive_add(x);
/* Reduction (sum) for the subgroup */
uint sg_sum = sub_group_broadcast(sg, 31);
/* Write out all the subgroups sums */
barrier(CLK_LOCAL_MEM_FENCE);
scratch[sg_id] = sg_sum;
barrier(CLK_LOCAL_MEM_FENCE);
/* Read all the subgroup sums. Thread T in subgroup G reads the sum of all
* threads in subgroup T.
*/
uint other_sum = scratch[get_sub_group_local_id()];
/* Exclusive sum the subgroup sums to get the total before the current group,
* which can be added to the total for the current group.
*/
uint other_sums = sub_group_scan_exclusive_add(other_sum);
uint base = sub_group_broadcast(other_sums, sg_id);
uint prefix = base + sg;
/* Reduce the workgroup using the prefix sum we already did */
uint reduction = sub_group_broadcast(other_sums + other_sum, 31);
return (uint2)(prefix, reduction);
}
KERNEL(1024)
_libagx_prefix_sum(global uint *buffer, uint len, uint words, uint word)
{
local uint scratch[32];
uint tid = cl_local_id.x;
/* Main loop: complete workgroups processing 1024 values at once */
uint i, count = 0;
uint len_remainder = len % 1024;
uint len_rounded_down = len - len_remainder;
for (i = tid; i < len_rounded_down; i += 1024) {
global uint *ptr = &buffer[(i * words) + word];
uint value = *ptr;
uint2 sums = libagx_work_group_scan_inclusive_add(value, scratch);
*ptr = count + sums[0];
count += sums[1];
}
/* The last iteration is special since we won't have a full subgroup unless
* the length is divisible by the subgroup size, and we don't advance count.
*/
global uint *ptr = &buffer[(i * words) + word];
uint value = (tid < len_remainder) ? *ptr : 0;
uint scan = libagx_work_group_scan_inclusive_add(value, scratch)[0];
if (tid < len_remainder) {
*ptr = count + scan;
}
}
KERNEL(1024)
libagx_prefix_sum_geom(constant struct agx_geometry_params *p)
{
_libagx_prefix_sum(p->count_buffer, p->input_primitives,
p->count_buffer_stride / 4, cl_group_id.x);
}
KERNEL(1024)
libagx_prefix_sum_tess(global struct libagx_tess_args *p, global uint *c_prims,
global uint *c_invs, uint increment_stats__2)
{
_libagx_prefix_sum(p->counts, p->nr_patches, 1 /* words */, 0 /* word */);
/* After prefix summing, we know the total # of indices, so allocate the
* index buffer now. Elect a thread for the allocation.
*/
barrier(CLK_LOCAL_MEM_FENCE);
if (cl_local_id.x != 0)
return;
/* The last element of an inclusive prefix sum is the total sum */
uint total = p->nr_patches > 0 ? p->counts[p->nr_patches - 1] : 0;
/* Allocate 4-byte indices */
uint32_t elsize_B = sizeof(uint32_t);
uint32_t size_B = total * elsize_B;
uint alloc_B = agx_heap_alloc_nonatomic_offs(p->heap, size_B);
p->index_buffer = (global uint32_t *)(((uintptr_t)p->heap->base) + alloc_B);
/* ...and now we can generate the API indexed draw */
global uint32_t *desc = p->out_draws;
desc[0] = total; /* count */
desc[1] = 1; /* instance_count */
desc[2] = alloc_B / elsize_B; /* start */
desc[3] = 0; /* index_bias */
desc[4] = 0; /* start_instance */
/* If necessary, increment clipper statistics too. This is only used when
* there's no geometry shader following us. See agx_nir_lower_gs.c for more
* info on the emulation. We just need to calculate the # of primitives
* tessellated.
*/
if (increment_stats__2) {
uint prims = p->points_mode ? total
: p->isolines ? (total / 2)
: (total / 3);
increment_counters(c_prims, c_invs, NULL, prims);
}
}
uintptr_t
libagx_vertex_output_address(uintptr_t buffer, uint64_t mask, uint vtx,
gl_varying_slot location)
{
/* Written like this to let address arithmetic work */
return buffer + ((uintptr_t)libagx_tcs_in_offs_el(vtx, location, mask)) * 16;
}
uintptr_t
libagx_geometry_input_address(constant struct agx_geometry_params *p, uint vtx,
gl_varying_slot location)
{
return libagx_vertex_output_address(p->input_buffer, p->input_mask, vtx,
location);
}
unsigned
libagx_input_vertices(constant struct agx_ia_state *ia)
{
return ia->verts_per_instance;
}
global uint *
libagx_load_xfb_count_address(constant struct agx_geometry_params *p, int index,
int count_words, uint unrolled_id)
{
return &p->count_buffer[(unrolled_id * count_words) + index];
}
uint
libagx_previous_xfb_primitives(global struct agx_geometry_params *p,
int static_count, int count_index,
int count_words, bool prefix_sum,
uint unrolled_id)
{
if (static_count >= 0) {
/* If the number of outputted vertices per invocation is known statically,
* we can calculate the base.
*/
return unrolled_id * static_count;
} else {
/* Otherwise, load from the count buffer buffer. Note that the sums are
* inclusive, so index 0 is nonzero. This requires a little fixup here. We
* use a saturating unsigned subtraction so we don't read out-of-bounds.
*
* If we didn't prefix sum, there's only one element.
*/
uint prim_minus_1 = prefix_sum ? sub_sat(unrolled_id, 1u) : 0;
uint count = p->count_buffer[(prim_minus_1 * count_words) + count_index];
return unrolled_id == 0 ? 0 : count;
}
}
/* Like u_foreach_bit, specialized for XFB to enable loop unrolling */
#define libagx_foreach_xfb(word, index) \
for (uint i = 0; i < 4; ++i) \
if (word & BITFIELD_BIT(i))
void
libagx_pre_gs(global struct agx_geometry_params *p, uint streams,
uint buffers_written, uint4 buffer_to_stream, int4 count_index,
uint4 stride, uint4 output_end, int4 static_count,
uint invocations, uint vertices_per_prim,
global uint *gs_invocations, global uint *gs_primitives,
global uint *c_primitives, global uint *c_invocations)
{
unsigned count_words = !!(count_index[0] >= 0) + !!(count_index[1] >= 0) +
!!(count_index[2] >= 0) + !!(count_index[3] >= 0);
bool prefix_sum = count_words && buffers_written;
uint unrolled_in_prims = p->input_primitives;
/* Determine the number of primitives generated in each stream */
uint4 in_prims = 0;
libagx_foreach_xfb(streams, i) {
in_prims[i] = libagx_previous_xfb_primitives(
p, static_count[i], count_index[i], count_words, prefix_sum,
unrolled_in_prims);
*(p->prims_generated_counter[i]) += in_prims[i];
}
uint4 prims = in_prims;
uint emitted_prims = prims[0] + prims[1] + prims[2] + prims[3];
if (buffers_written) {
libagx_foreach_xfb(buffers_written, i) {
uint max_prims = libagx_setup_xfb_buffer(
p, i, stride[i], output_end[i], vertices_per_prim);
unsigned stream = buffer_to_stream[i];
prims[stream] = min(prims[stream], max_prims);
}
int4 overflow = prims < in_prims;
libagx_foreach_xfb(streams, i) {
p->xfb_verts[i] = prims[i] * vertices_per_prim;
*(p->xfb_overflow[i]) += (bool)overflow[i];
*(p->xfb_prims_generated_counter[i]) += prims[i];
}
*(p->xfb_any_overflow) += any(overflow);
/* Update XFB counters */
libagx_foreach_xfb(buffers_written, i) {
uint32_t prim_stride_B = stride[i] * vertices_per_prim;
unsigned stream = buffer_to_stream[i];
global uint *ptr = p->xfb_offs_ptrs[i];
if ((uintptr_t)ptr == AGX_ZERO_PAGE_ADDRESS) {
ptr = (global uint *)AGX_SCRATCH_PAGE_ADDRESS;
}
*ptr += prims[stream] * prim_stride_B;
}
}
/* The geometry shader is invoked once per primitive (after unrolling
* primitive restart). From the spec:
*
* In case of instanced geometry shaders (see section 11.3.4.2) the
* geometry shader invocations count is incremented for each separate
* instanced invocation.
*/
*gs_invocations += unrolled_in_prims * invocations;
*gs_primitives += emitted_prims;
/* Clipper queries are not well-defined, so we can emulate them in lots of
* silly ways. We need the hardware counters to implement them properly. For
* now, just consider all primitives emitted as passing through the clipper.
* This satisfies spec text:
*
* The number of primitives that reach the primitive clipping stage.
*
* and
*
* If at least one vertex of the primitive lies inside the clipping
* volume, the counter is incremented by one or more. Otherwise, the
* counter is incremented by zero or more.
*/
*c_primitives += emitted_prims;
*c_invocations += emitted_prims;
}