poly: Migrate AGX's GS/TESS emulation to common code

This moves most of the code to a new home: src/poly.
Most precomp kernels logic that could be moved are provided by poly now.

Signed-off-by: Mary Guillemard <mary.guillemard@collabora.com>
Acked-by: Alyssa Rosenzweig <alyssa.rosenzweig@intel.com>
Part-of: <https://gitlab.freedesktop.org/mesa/mesa/-/merge_requests/37914>
This commit is contained in:
Mary Guillemard
2025-10-06 12:57:04 +02:00
committed by Marge Bot
parent 8048004238
commit b2accf86d1
35 changed files with 3421 additions and 3117 deletions
+8
View File
@@ -0,0 +1,8 @@
BasedOnStyle: InheritParentConfig
DisableFormat: false
AlignConsecutiveBitFields: Consecutive
ColumnLimit: 80
BreakStringLiterals: false
SpaceBeforeParens: ControlStatementsExceptControlMacros
+501
View File
@@ -0,0 +1,501 @@
/*
* Copyright 2023 Alyssa Rosenzweig
* Copyright 2023 Valve Corporation
* Copyright 2025 Collabora Ltd.
* SPDX-License-Identifier: MIT
*/
#include "compiler/libcl/libcl_vk.h"
#include "poly/geometry.h"
#include "poly/tessellator.h"
#include "util/macros.h"
#include "util/u_math.h"
uint64_t nir_ro_to_rw_poly(uint64_t address);
/* 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.
*/
static uint
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
poly_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
poly_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 = 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
poly_xfb_vertex_address(constant struct poly_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;
}
static uint
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
poly_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;
}
static uint
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
poly_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
poly_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;
}
static uint
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
poly_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 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 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 + map_vertex_in_tri_strip(prim, vert, flatshade_first);
}
case MESA_PRIM_TRIANGLE_FAN:
return vertex_id_for_tri_fan(prim, vert, flatshade_first);
case MESA_PRIM_TRIANGLE_STRIP_ADJACENCY:
return vertex_id_for_tri_strip_adj(prim, vert, num_prims,
flatshade_first);
default:
return 0;
}
}
uint
poly_map_to_line_adj(uint id)
{
/* Sequence (1, 2), (5, 6), (9, 10), ... */
return ((id & ~1) * 2) + (id & 1) + 1;
}
uint
poly_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
poly_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);
}
uint
poly_load_index_buffer(constant struct poly_ia_state *p, uint id,
uint index_size)
{
return poly_load_index(p->index_buffer, p->index_buffer_range_el, id,
index_size);
}
static uint
setup_xfb_buffer(global struct poly_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
poly_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)
{
_poly_write_strip(index_buffer, inv_index_offset + prim_index_offset,
vertex_offset, verts_in_prim, info.x, info.y, info.z);
}
void
poly_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;
}
}
uintptr_t
poly_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)poly_tcs_in_offs_el(vtx, location, mask)) * 16;
}
uintptr_t
poly_geometry_input_address(constant struct poly_geometry_params *p, uint vtx,
gl_varying_slot location)
{
return poly_vertex_output_address(p->input_buffer, p->input_mask, vtx,
location);
}
unsigned
poly_input_vertices(constant struct poly_ia_state *ia)
{
return ia->verts_per_instance;
}
global uint *
poly_load_xfb_count_address(constant struct poly_geometry_params *p, int index,
int count_words, uint unrolled_id)
{
return &p->count_buffer[(unrolled_id * count_words) + index];
}
uint
poly_previous_xfb_primitives(global struct poly_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 poly_foreach_xfb(word, index) \
for (uint i = 0; i < 4; ++i) \
if (word & BITFIELD_BIT(i))
void
poly_pre_gs(global struct poly_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;
poly_foreach_xfb(streams, i) {
in_prims[i] = poly_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) {
poly_foreach_xfb(buffers_written, i) {
uint max_prims =
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;
poly_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 */
poly_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];
ptr = (global uint *)nir_ro_to_rw_poly((uint64_t)ptr);
*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;
}
+35
View File
@@ -0,0 +1,35 @@
# Copyright 2024 Valve Corporation
# Copyright © 2025 Collabora Ltd.
# SPDX-License-Identifier: MIT
libpoly_shader_files = files(
'geometry.cl',
'tessellation.cl',
)
libpoly_shaders_spv = custom_target(
input : libpoly_shader_files,
output : 'libpoly.spv',
command : [
prog_mesa_clc, '-o', '@OUTPUT@', '--depfile', '@DEPFILE@',
libpoly_shader_files, '--',
'-I' + join_paths(meson.project_source_root(), 'include'),
'-I' + join_paths(meson.project_source_root(), 'src/compiler/libcl'),
'-I' + join_paths(meson.current_source_dir(), '.'),
'-I' + join_paths(meson.current_source_dir(), '../../'),
cl_args,
],
depends : [],
depfile : 'libpoly_shaders.h.d',
)
libpoly_shaders = custom_target(
input : libpoly_shaders_spv,
output : ['libpoly.cpp', 'libpoly.h'],
command : [prog_vtn_bindgen2, libpoly_shaders_spv, '@OUTPUT0@', '@OUTPUT1@'],
)
idep_libpoly = declare_dependency(
sources : [libpoly_shaders],
include_directories : include_directories('.'),
)
+133
View File
@@ -0,0 +1,133 @@
/*
* Copyright 2023 Alyssa Rosenzweig
* SPDX-License-Identifier: MIT
*/
#include "poly/geometry.h"
#include "poly/tessellator.h"
uint
poly_tcs_patch_vertices_in(constant struct poly_tess_args *p)
{
return p->input_patch_size;
}
uint
poly_tes_patch_vertices_in(constant struct poly_tess_args *p)
{
return p->output_patch_size;
}
uint
poly_tcs_unrolled_id(constant struct poly_tess_args *p, uint3 wg_id)
{
return (wg_id.y * p->patches_per_instance) + wg_id.x;
}
uint64_t
poly_tes_buffer(constant struct poly_tess_args *p)
{
return p->tes_buffer;
}
/*
* Helper to lower indexing for a tess eval shader ran as a compute shader. This
* handles the tess+geom case. This is simpler than the general input assembly
* lowering, as we know:
*
* 1. the index buffer is U32
* 2. the index is in bounds
*
* Therefore we do a simple load. No bounds checking needed.
*/
uint32_t
poly_load_tes_index(constant struct poly_tess_args *p, uint32_t index)
{
/* Swap second and third vertices of each triangle to flip winding order
* dynamically if needed.
*/
if (p->ccw) {
uint id = index % 3;
if (id == 1)
index++;
else if (id == 2)
index--;
}
return p->index_buffer[index];
}
uintptr_t
poly_tcs_out_address(constant struct poly_tess_args *p, uint patch_id,
uint vtx_id, gl_varying_slot location, uint nr_patch_out,
uint out_patch_size, uint64_t vtx_out_mask)
{
uint stride_el =
poly_tcs_out_stride_el(nr_patch_out, out_patch_size, vtx_out_mask);
uint offs_el =
poly_tcs_out_offs_el(vtx_id, location, nr_patch_out, vtx_out_mask);
offs_el += patch_id * stride_el;
/* Written to match the AGX addressing mode */
return (uintptr_t)(p->tcs_buffer) + (((uintptr_t)offs_el) << 2);
}
static uint
tes_unrolled_patch_id(uint raw_id)
{
return raw_id / POLY_TES_PATCH_ID_STRIDE;
}
uint
poly_tes_patch_id(constant struct poly_tess_args *p, uint raw_id)
{
return tes_unrolled_patch_id(raw_id) % p->patches_per_instance;
}
static uint
tes_vertex_id_in_patch(uint raw_id)
{
return raw_id % POLY_TES_PATCH_ID_STRIDE;
}
float2
poly_load_tess_coord(constant struct poly_tess_args *p, uint raw_id)
{
uint patch = tes_unrolled_patch_id(raw_id);
uint vtx = tes_vertex_id_in_patch(raw_id);
global struct poly_tess_point *t =
&p->patch_coord_buffer[p->coord_allocs[patch] + vtx];
/* Written weirdly because NIR struggles with loads of structs */
uint2 fixed = *((global uint2 *)t);
/* Convert fixed point to float */
return convert_float2(fixed) / (1u << 16);
}
uintptr_t
poly_tes_in_address(constant struct poly_tess_args *p, uint raw_id, uint vtx_id,
gl_varying_slot location)
{
uint patch = tes_unrolled_patch_id(raw_id);
return poly_tcs_out_address(p, patch, vtx_id, location,
p->tcs_patch_constants, p->output_patch_size,
p->tcs_per_vertex_outputs);
}
float4
poly_tess_level_outer_default(constant struct poly_tess_args *p)
{
return vload4(0, p->tess_level_outer_default);
}
float2
poly_tess_level_inner_default(constant struct poly_tess_args *p)
{
return vload2(0, p->tess_level_inner_default);
}
File diff suppressed because it is too large Load Diff
+641
View File
@@ -0,0 +1,641 @@
/*
* Copyright 2023 Alyssa Rosenzweig
* Copyright 2023 Valve Corporation
* SPDX-License-Identifier: MIT
*/
#include "compiler/libcl/libcl.h"
#include "compiler/shader_enums.h"
#include "util/bitscan.h"
#include "util/u_math.h"
#ifdef __OPENCL_VERSION__
#include "compiler/libcl/libcl_vk.h"
#endif
#pragma once
#define POLY_MAX_SO_BUFFERS 4
#define POLY_MAX_VERTEX_STREAMS 4
enum poly_gs_shape {
/* Indexed, where indices are encoded as:
*
* round_to_pot(max_indices) * round_to_pot(input_primitives) *
* * instance_count
*
* invoked for max_indices * input_primitives * instance_count indices.
*
* This is used with any dynamic topology. No hardware instancing used.
*/
POLY_GS_SHAPE_DYNAMIC_INDEXED,
/* Indexed with a static index buffer. Indices ranges up to max_indices.
* Hardware instance count = input_primitives * software instance count.
*/
POLY_GS_SHAPE_STATIC_INDEXED,
/* Non-indexed. Dispatched as:
*
* (max_indices, input_primitives * instance count).
*/
POLY_GS_SHAPE_STATIC_PER_PRIM,
/* Non-indexed. Dispatched as:
*
* (max_indices * input_primitives, instance count).
*/
POLY_GS_SHAPE_STATIC_PER_INSTANCE,
};
static inline unsigned
poly_gs_rast_vertices(enum poly_gs_shape shape, unsigned max_indices,
unsigned input_primitives, unsigned instance_count)
{
switch (shape) {
case POLY_GS_SHAPE_DYNAMIC_INDEXED:
return max_indices * input_primitives * instance_count;
case POLY_GS_SHAPE_STATIC_INDEXED:
case POLY_GS_SHAPE_STATIC_PER_PRIM:
return max_indices;
case POLY_GS_SHAPE_STATIC_PER_INSTANCE:
return max_indices * input_primitives;
}
UNREACHABLE("invalid shape");
}
static inline unsigned
poly_gs_rast_instances(enum poly_gs_shape shape, unsigned max_indices,
unsigned input_primitives, unsigned instance_count)
{
switch (shape) {
case POLY_GS_SHAPE_DYNAMIC_INDEXED:
return 1;
case POLY_GS_SHAPE_STATIC_INDEXED:
case POLY_GS_SHAPE_STATIC_PER_PRIM:
return input_primitives * instance_count;
case POLY_GS_SHAPE_STATIC_PER_INSTANCE:
return instance_count;
}
UNREACHABLE("invalid shape");
}
static inline bool
poly_gs_indexed(enum poly_gs_shape shape)
{
return shape == POLY_GS_SHAPE_DYNAMIC_INDEXED ||
shape == POLY_GS_SHAPE_STATIC_INDEXED;
}
static inline unsigned
poly_gs_index_size(enum poly_gs_shape shape)
{
switch (shape) {
case POLY_GS_SHAPE_DYNAMIC_INDEXED:
return 4;
case POLY_GS_SHAPE_STATIC_INDEXED:
return 1;
default:
return 0;
}
}
/* Heap to allocate from. */
struct poly_heap {
DEVICE(uchar) base;
uint32_t bottom, size;
} PACKED;
static_assert(sizeof(struct poly_heap) == 4 * 4);
#ifdef __OPENCL_VERSION__
static inline uint
_poly_heap_alloc_offs(global struct poly_heap *heap, uint size_B, bool atomic)
{
size_B = align(size_B, 16);
uint offs;
if (atomic) {
offs = atomic_fetch_add((volatile atomic_uint *)(&heap->bottom), size_B);
} else {
offs = heap->bottom;
heap->bottom = offs + size_B;
}
/* Use printf+abort because assert is stripped from release builds. */
if (heap->bottom >= heap->size) {
printf(
"FATAL: GPU heap overflow, allocating size %u, at offset %u, heap size %u!",
size_B, offs, heap->size);
abort();
}
return offs;
}
static inline uint
poly_heap_alloc_nonatomic_offs(global struct poly_heap *heap, uint size_B)
{
return _poly_heap_alloc_offs(heap, size_B, false);
}
static inline uint
poly_heap_alloc_atomic_offs(global struct poly_heap *heap, uint size_B)
{
return _poly_heap_alloc_offs(heap, size_B, true);
}
static inline global void *
poly_heap_alloc_nonatomic(global struct poly_heap *heap, uint size_B)
{
return heap->base + poly_heap_alloc_nonatomic_offs(heap, size_B);
}
uint64_t nir_load_ro_sink_address_poly(void);
static inline uint64_t
poly_index_buffer(uint64_t index_buffer, uint size_el, uint offset_el,
uint elsize_B)
{
if (offset_el < size_el)
return index_buffer + (offset_el * elsize_B);
else
return nir_load_ro_sink_address_poly();
}
#endif
struct poly_ia_state {
/* Index buffer if present. */
uint64_t index_buffer;
/* Size of the bound index buffer for bounds checking */
uint32_t index_buffer_range_el;
/* Number of vertices per instance. Written by CPU for direct draw, indirect
* setup kernel for indirect. This is used for VS->GS and VS->TCS indexing.
*/
uint32_t verts_per_instance;
} PACKED;
static_assert(sizeof(struct poly_ia_state) == 4 * 4);
static inline uint
poly_index_buffer_range_el(uint size_el, uint offset_el)
{
return offset_el < size_el ? (size_el - offset_el) : 0;
}
struct poly_geometry_params {
/* Address of associated indirect draw buffer */
DEVICE(uint) indirect_desc;
/* Address of count buffer. For an indirect draw, this will be written by the
* indirect setup kernel.
*/
DEVICE(uint) count_buffer;
/* Address of the primitives generated counters */
DEVICE(uint) prims_generated_counter[POLY_MAX_VERTEX_STREAMS];
DEVICE(uint) xfb_prims_generated_counter[POLY_MAX_VERTEX_STREAMS];
DEVICE(uint) xfb_overflow[POLY_MAX_VERTEX_STREAMS];
DEVICE(uint) xfb_any_overflow;
/* Pointers to transform feedback buffer offsets in bytes */
DEVICE(uint) xfb_offs_ptrs[POLY_MAX_SO_BUFFERS];
/* Output index buffer, allocated by pre-GS. */
DEVICE(uint) output_index_buffer;
/* Address of transform feedback buffer in general, supplied by the CPU. */
DEVICE(uchar) xfb_base_original[POLY_MAX_SO_BUFFERS];
/* Address of transform feedback for the current primitive. Written by pre-GS
* program.
*/
DEVICE(uchar) xfb_base[POLY_MAX_SO_BUFFERS];
/* Address and present mask for the input to the geometry shader. These will
* reflect the vertex shader for VS->GS or instead the tessellation
* evaluation shader for TES->GS.
*/
uint64_t input_buffer;
uint64_t input_mask;
/* Location-indexed mask of flat outputs, used for lowering GL edge flags. */
uint64_t flat_outputs;
uint32_t xfb_size[POLY_MAX_SO_BUFFERS];
/* Number of vertices emitted by transform feedback per stream. Written by
* the pre-GS program.
*/
uint32_t xfb_verts[POLY_MAX_VERTEX_STREAMS];
/* Within an indirect GS draw, the grids used to dispatch the VS/GS written
* out by the GS indirect setup kernel or the CPU for a direct draw. This is
* the "indirect local" format: first 3 is in threads, second 3 is in grid
* blocks. This lets us use nontrivial workgroups with indirect draws without
* needing any predication.
*/
uint32_t vs_grid[6];
uint32_t gs_grid[6];
/* Number of input primitives across all instances, calculated by the CPU for
* a direct draw or the GS indirect setup kernel for an indirect draw.
*/
uint32_t input_primitives;
/* Number of input primitives per instance, rounded up to a power-of-two and
* with the base-2 log taken. This is used to partition the output vertex IDs
* efficiently.
*/
uint32_t primitives_log2;
/* Number of bytes output by the GS count shader per input primitive (may be
* 0), written by CPU and consumed by indirect draw setup shader for
* allocating counts.
*/
uint32_t count_buffer_stride;
/* Dynamic input topology. Must be compatible with the geometry shader's
* layout() declared input class.
*/
uint32_t input_topology;
} PACKED;
static_assert(sizeof(struct poly_geometry_params) == 86 * 4);
/* TCS shared memory layout:
*
* vec4 vs_outputs[VERTICES_IN_INPUT_PATCH][TOTAL_VERTEX_OUTPUTS];
*
* TODO: compact.
*/
static inline uint
poly_tcs_in_offs_el(uint vtx, gl_varying_slot location,
uint64_t crosslane_vs_out_mask)
{
uint base = vtx * util_bitcount64(crosslane_vs_out_mask);
uint offs = util_bitcount64(crosslane_vs_out_mask &
(((uint64_t)(1) << location) - 1));
return base + offs;
}
static inline uint
poly_tcs_in_size(uint32_t vertices_in_patch, uint64_t crosslane_vs_out_mask)
{
return vertices_in_patch * util_bitcount64(crosslane_vs_out_mask) * 16;
}
/*
* TCS out buffer layout, per-patch:
*
* float tess_level_outer[4];
* float tess_level_inner[2];
* vec4 patch_out[MAX_PATCH_OUTPUTS];
* vec4 vtx_out[OUT_PATCH_SIZE][TOTAL_VERTEX_OUTPUTS];
*
* Vertex out are compacted based on the mask of written out. Patch
* out are used as-is.
*
* Bounding boxes are ignored.
*/
static inline uint
poly_tcs_out_offs_el(uint vtx_id, gl_varying_slot location, uint nr_patch_out,
uint64_t vtx_out_mask)
{
uint off = 0;
if (location == VARYING_SLOT_TESS_LEVEL_OUTER)
return off;
off += 4;
if (location == VARYING_SLOT_TESS_LEVEL_INNER)
return off;
off += 2;
if (location >= VARYING_SLOT_PATCH0)
return off + (4 * (location - VARYING_SLOT_PATCH0));
/* Anything else is a per-vtx output */
off += 4 * nr_patch_out;
off += 4 * vtx_id * util_bitcount64(vtx_out_mask);
uint idx = util_bitcount64(vtx_out_mask & (((uint64_t)(1) << location) - 1));
return off + (4 * idx);
}
static inline uint
poly_tcs_out_stride_el(uint nr_patch_out, uint out_patch_size,
uint64_t vtx_out_mask)
{
return poly_tcs_out_offs_el(out_patch_size, VARYING_SLOT_POS, nr_patch_out,
vtx_out_mask);
}
static inline uint
poly_tcs_out_stride(uint nr_patch_out, uint out_patch_size,
uint64_t vtx_out_mask)
{
return poly_tcs_out_stride_el(nr_patch_out, out_patch_size, vtx_out_mask) *
4;
}
/* In a tess eval shader, stride for hw vertex ID */
#define POLY_TES_PATCH_ID_STRIDE 8192
static inline uint
poly_compact_prim(enum mesa_prim prim)
{
static_assert(MESA_PRIM_QUAD_STRIP == MESA_PRIM_QUADS + 1);
static_assert(MESA_PRIM_POLYGON == MESA_PRIM_QUADS + 2);
#ifndef __OPENCL_VERSION__
assert(prim != MESA_PRIM_QUADS);
assert(prim != MESA_PRIM_QUAD_STRIP);
assert(prim != MESA_PRIM_POLYGON);
assert(prim != MESA_PRIM_PATCHES);
#endif
return (prim >= MESA_PRIM_QUADS) ? (prim - 3) : prim;
}
static inline enum mesa_prim
poly_uncompact_prim(uint packed)
{
if (packed >= MESA_PRIM_QUADS)
return (enum mesa_prim)(packed + 3);
return (enum mesa_prim)packed;
}
/*
* Write a strip into a 32-bit index buffer. This is the sequence:
*
* (b, b + 1, b + 2, ..., b + n - 1, -1) where -1 is the restart index
*
* For points, we write index buffers without restart just for remapping.
*/
static inline void
_poly_write_strip(GLOBAL uint32_t *index_buffer, uint32_t index_offset,
uint32_t vertex_offset, uint32_t verts_in_prim,
uint32_t stream, uint32_t stream_multiplier, uint32_t n)
{
bool restart = n > 1;
if (verts_in_prim < n)
return;
GLOBAL uint32_t *out = &index_buffer[index_offset];
/* Write out indices for the strip */
for (uint32_t i = 0; i < verts_in_prim; ++i) {
out[i] = (vertex_offset + i) * stream_multiplier + stream;
}
if (restart)
out[verts_in_prim] = -1;
}
static inline unsigned
poly_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);
}
}
#ifdef __OPENCL_VERSION__
/*
* 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 inline uint2
poly_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);
}
static inline void
poly_prefix_sum(local uint *scratch, global uint *buffer, uint len, uint words,
uint word, uint wg_count)
{
uint tid = cl_local_id.x;
/* Main loop: complete workgroups processing multiple values at once */
uint i, count = 0;
uint len_remainder = len % wg_count;
uint len_rounded_down = len - len_remainder;
for (i = tid; i < len_rounded_down; i += wg_count) {
global uint *ptr = &buffer[(i * words) + word];
uint value = *ptr;
uint2 sums = poly_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 = poly_work_group_scan_inclusive_add(value, scratch)[0];
if (tid < len_remainder) {
*ptr = count + scan;
}
}
static inline void
poly_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 inline void
poly_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)
{
poly_increment_counters(ia_vertices, vs_invocations, NULL,
draw[0] * draw[1]);
uint prims = poly_decomposed_prims_for_vertices_with_tess(prim, draw[0],
verts_per_patch) *
draw[1];
poly_increment_counters(ia_primitives, c_prims, c_invs, prims);
}
static inline void
poly_gs_setup_indirect(uint64_t index_buffer, constant uint *draw,
global uintptr_t *vertex_buffer /* output */,
global struct poly_ia_state *ia /* output */,
global struct poly_geometry_params *p /* output */,
global struct poly_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 poly_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 = poly_index_buffer(index_buffer, index_buffer_range_el,
draw[2], index_size_B);
ia->index_buffer_range_el =
poly_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 =
poly_tcs_in_size(vertex_count * instance_count, vs_outputs);
if (is_prefix_summing) {
p->count_buffer = poly_heap_alloc_nonatomic(
heap, p->input_primitives * p->count_buffer_stride);
}
p->input_buffer =
(uintptr_t)poly_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 = poly_gs_rast_vertices(shape, max_indices, prim_per_instance,
instance_count),
.instanceCount = poly_gs_rast_instances(
shape, max_indices, prim_per_instance, instance_count),
};
if (shape == POLY_GS_SHAPE_DYNAMIC_INDEXED) {
cmd->firstIndex =
poly_heap_alloc_nonatomic_offs(heap, cmd->indexCount * 4) / 4;
p->output_index_buffer =
(global uint *)(heap->base + (cmd->firstIndex * 4));
}
}
static uint
poly_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;
}
static void
poly_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;
}
#endif
+9
View File
@@ -0,0 +1,9 @@
# Copyright © 2025 Collabora Ltd.
# SPDX-License-Identifier: MIT
inc_poly = include_directories([
'.', 'nir'
])
subdir('cl')
subdir('nir')
+18
View File
@@ -0,0 +1,18 @@
# Copyright © 2025 Collabora Ltd.
# SPDX-License-Identifier: MIT
libpoly_nir_files = files(
'poly_nir_lower_gs.c',
'poly_nir_lower_ia.c',
'poly_nir_lower_tess.c',
)
libpoly_nir = static_library(
'libpoly_nir',
[libpoly_nir_files],
include_directories : [inc_poly],
c_args : [no_override_init_args, '-Wno-c2x-extensions'],
gnu_symbol_visibility : 'hidden',
dependencies: [idep_nir, idep_mesautil, idep_libpoly],
build_by_default : false,
)
File diff suppressed because it is too large Load Diff
+61
View File
@@ -0,0 +1,61 @@
/*
* Copyright 2023 Alyssa Rosenzweig
* SPDX-License-Identifier: MIT
*/
#pragma once
#include <stdbool.h>
#include <stdint.h>
#include "poly/geometry.h"
#include "nir.h"
#include "shader_enums.h"
struct nir_def *poly_load_per_vertex_input(struct nir_builder *b,
nir_intrinsic_instr *intr,
struct nir_def *vertex);
nir_def *poly_nir_load_vertex_id(struct nir_builder *b, nir_def *id,
unsigned index_size_B);
bool poly_nir_lower_sw_vs(struct nir_shader *s, unsigned index_size_B);
bool poly_nir_lower_vs_before_gs(struct nir_shader *vs);
struct poly_gs_info {
/* Output primitive mode for geometry shaders */
enum mesa_prim mode;
/* Number of words per primitive in the count buffer */
unsigned count_words;
/* Per-input primitive stride of the output index buffer */
unsigned max_indices;
/* Whether the GS includes transform feedback at a compile-time level */
bool xfb;
/* Whether a prefix sum is required on the count outputs. Implies xfb */
bool prefix_sum;
/* Whether the GS writes to a stream other than stream #0 */
bool multistream;
/* Shape of the rasterization draw, named by the instance ID */
enum poly_gs_shape shape;
/* Static topology used if shape = POLY_GS_SHAPE_STATIC_INDEXED */
uint8_t topology[64];
};
bool poly_nir_lower_gs(struct nir_shader *gs, struct nir_shader **gs_count,
struct nir_shader **gs_copy, struct nir_shader **pre_gs,
struct poly_gs_info *info);
bool poly_nir_lower_tcs(struct nir_shader *tcs);
bool poly_nir_lower_tes(struct nir_shader *tes, bool to_hw_vs);
uint64_t poly_tcs_per_vertex_outputs(const struct nir_shader *nir);
unsigned poly_tcs_output_stride(const struct nir_shader *nir);
+64
View File
@@ -0,0 +1,64 @@
/*
* Copyright 2023 Valve Corporation
* SPDX-License-Identifier: MIT
*/
#include "compiler/nir/nir_builder.h"
#include "poly/cl/libpoly.h"
#include "poly/geometry.h"
#include "nir.h"
/* XXX: Remove me later */
nir_def *poly_nir_load_vertex_id(struct nir_builder *b, nir_def *id,
unsigned index_size_B);
bool poly_nir_lower_sw_vs(struct nir_shader *s, unsigned index_size_B);
/*
* This file implements basic input assembly in software. It runs on software
* vertex shaders, as part of geometry/tessellation lowering. It does not apply
* the topology, which happens in the geometry shader.
*/
nir_def *
poly_nir_load_vertex_id(nir_builder *b, nir_def *id, unsigned index_size_B)
{
/* If drawing with an index buffer, pull the vertex ID. Otherwise, the
* vertex ID is just the index as-is.
*/
if (index_size_B) {
nir_def *ia = nir_load_input_assembly_buffer_poly(b);
id = poly_load_index_buffer(b, ia, id, nir_imm_int(b, index_size_B));
}
/* Add the "start", either an index bias or a base vertex. This must happen
* after indexing for proper index bias behaviour.
*/
return nir_iadd(b, id, nir_load_first_vertex(b));
}
static bool
lower(nir_builder *b, nir_intrinsic_instr *intr, void *data)
{
unsigned *index_size_B = data;
b->cursor = nir_before_instr(&intr->instr);
if (intr->intrinsic == nir_intrinsic_load_vertex_id) {
nir_def *id = nir_channel(b, nir_load_global_invocation_id(b, 32), 0);
nir_def_replace(&intr->def,
poly_nir_load_vertex_id(b, id, *index_size_B));
return true;
} else if (intr->intrinsic == nir_intrinsic_load_instance_id) {
nir_def_replace(&intr->def,
nir_channel(b, nir_load_global_invocation_id(b, 32), 1));
return true;
}
return false;
}
bool
poly_nir_lower_sw_vs(nir_shader *s, unsigned index_size_B)
{
return nir_shader_intrinsics_pass(s, lower, nir_metadata_control_flow,
&index_size_B);
}
+268
View File
@@ -0,0 +1,268 @@
/*
* Copyright 2023 Alyssa Rosenzweig
* SPDX-License-Identifier: MIT
*/
#include "poly/cl/libpoly.h"
#include "poly/geometry.h"
#include "poly/nir/poly_nir_lower_gs.h"
#include "util/bitscan.h"
#include "util/macros.h"
#include "nir.h"
#include "nir_builder.h"
#include "nir_builder_opcodes.h"
#include "nir_intrinsics.h"
#include "nir_intrinsics_indices.h"
#include "shader_enums.h"
static nir_def *
tcs_unrolled_id(nir_builder *b)
{
return poly_tcs_unrolled_id(b, nir_load_tess_param_buffer_poly(b),
nir_load_workgroup_id(b));
}
uint64_t
poly_tcs_per_vertex_outputs(const nir_shader *nir)
{
return nir->info.outputs_written &
~(VARYING_BIT_TESS_LEVEL_INNER | VARYING_BIT_TESS_LEVEL_OUTER |
VARYING_BIT_BOUNDING_BOX0 | VARYING_BIT_BOUNDING_BOX1);
}
unsigned
poly_tcs_output_stride(const nir_shader *nir)
{
return poly_tcs_out_stride(util_last_bit(nir->info.patch_outputs_written),
nir->info.tess.tcs_vertices_out,
poly_tcs_per_vertex_outputs(nir));
}
static nir_def *
tcs_out_addr(nir_builder *b, nir_intrinsic_instr *intr, nir_def *vertex_id)
{
nir_io_semantics sem = nir_intrinsic_io_semantics(intr);
nir_def *offset = nir_get_io_offset_src(intr)->ssa;
nir_def *addr = poly_tcs_out_address(
b, nir_load_tess_param_buffer_poly(b), tcs_unrolled_id(b), vertex_id,
nir_iadd_imm(b, offset, sem.location),
nir_imm_int(b, util_last_bit(b->shader->info.patch_outputs_written)),
nir_imm_int(b, b->shader->info.tess.tcs_vertices_out),
nir_imm_int64(b, poly_tcs_per_vertex_outputs(b->shader)));
addr = nir_iadd_imm(b, addr, nir_intrinsic_component(intr) * 4);
return addr;
}
static nir_def *
lower_tes_load(nir_builder *b, nir_intrinsic_instr *intr)
{
gl_varying_slot location = nir_intrinsic_io_semantics(intr).location;
nir_src *offset_src = nir_get_io_offset_src(intr);
nir_def *vertex = nir_imm_int(b, 0);
nir_def *offset = offset_src ? offset_src->ssa : nir_imm_int(b, 0);
if (intr->intrinsic == nir_intrinsic_load_per_vertex_input)
vertex = intr->src[0].ssa;
nir_def *addr = poly_tes_in_address(b, nir_load_tess_param_buffer_poly(b),
nir_load_vertex_id(b), vertex,
nir_iadd_imm(b, offset, location));
if (nir_intrinsic_has_component(intr))
addr = nir_iadd_imm(b, addr, nir_intrinsic_component(intr) * 4);
return nir_load_global_constant(b, addr, 4, intr->def.num_components,
intr->def.bit_size);
}
static nir_def *
tcs_load_input(nir_builder *b, nir_intrinsic_instr *intr)
{
nir_def *base = nir_imul(
b, tcs_unrolled_id(b),
poly_tcs_patch_vertices_in(b, nir_load_tess_param_buffer_poly(b)));
nir_def *vertex = nir_iadd(b, base, intr->src[0].ssa);
return poly_load_per_vertex_input(b, intr, vertex);
}
static nir_def *
lower_tcs_impl(nir_builder *b, nir_intrinsic_instr *intr)
{
switch (intr->intrinsic) {
case nir_intrinsic_barrier:
/* A patch fits in a subgroup, so the barrier is unnecessary. */
return NIR_LOWER_INSTR_PROGRESS_REPLACE;
case nir_intrinsic_load_primitive_id:
return nir_channel(b, nir_load_workgroup_id(b), 0);
case nir_intrinsic_load_instance_id:
return nir_channel(b, nir_load_workgroup_id(b), 1);
case nir_intrinsic_load_invocation_id:
if (b->shader->info.tess.tcs_vertices_out == 1)
return nir_imm_int(b, 0);
else
return nir_channel(b, nir_load_local_invocation_id(b), 0);
case nir_intrinsic_load_per_vertex_input:
return tcs_load_input(b, intr);
case nir_intrinsic_load_patch_vertices_in:
return poly_tcs_patch_vertices_in(b, nir_load_tess_param_buffer_poly(b));
case nir_intrinsic_load_tess_level_outer_default:
return poly_tess_level_outer_default(b,
nir_load_tess_param_buffer_poly(b));
case nir_intrinsic_load_tess_level_inner_default:
return poly_tess_level_inner_default(b,
nir_load_tess_param_buffer_poly(b));
case nir_intrinsic_load_output: {
nir_def *addr = tcs_out_addr(b, intr, nir_undef(b, 1, 32));
return nir_load_global(b, addr, 4, intr->def.num_components,
intr->def.bit_size);
}
case nir_intrinsic_load_per_vertex_output: {
nir_def *addr = tcs_out_addr(b, intr, intr->src[0].ssa);
return nir_load_global(b, addr, 4, intr->def.num_components,
intr->def.bit_size);
}
case nir_intrinsic_store_output: {
/* Only vec2, make sure we can't overwrite */
assert(intr->src[0].ssa->num_components <= 2 ||
nir_intrinsic_io_semantics(intr).location !=
VARYING_SLOT_TESS_LEVEL_INNER);
nir_store_global(b, tcs_out_addr(b, intr, nir_undef(b, 1, 32)), 4,
intr->src[0].ssa, nir_intrinsic_write_mask(intr));
return NIR_LOWER_INSTR_PROGRESS_REPLACE;
}
case nir_intrinsic_store_per_vertex_output: {
nir_store_global(b, tcs_out_addr(b, intr, intr->src[1].ssa), 4,
intr->src[0].ssa, nir_intrinsic_write_mask(intr));
return NIR_LOWER_INSTR_PROGRESS_REPLACE;
}
default:
return NULL;
}
}
static bool
lower_tcs(nir_builder *b, nir_intrinsic_instr *intr, void *data)
{
b->cursor = nir_before_instr(&intr->instr);
nir_def *repl = lower_tcs_impl(b, intr);
if (!repl)
return false;
if (repl != NIR_LOWER_INSTR_PROGRESS_REPLACE)
nir_def_rewrite_uses(&intr->def, repl);
nir_instr_remove(&intr->instr);
return true;
}
bool
poly_nir_lower_tcs(nir_shader *tcs)
{
return nir_shader_intrinsics_pass(tcs, lower_tcs, nir_metadata_control_flow,
NULL);
}
static nir_def *
lower_tes_impl(nir_builder *b, nir_intrinsic_instr *intr, void *data)
{
switch (intr->intrinsic) {
case nir_intrinsic_load_tess_coord_xy:
return poly_load_tess_coord(b, nir_load_tess_param_buffer_poly(b),
nir_load_vertex_id(b));
case nir_intrinsic_load_primitive_id:
return poly_tes_patch_id(b, nir_load_tess_param_buffer_poly(b),
nir_load_vertex_id(b));
case nir_intrinsic_load_input:
case nir_intrinsic_load_per_vertex_input:
case nir_intrinsic_load_tess_level_inner:
case nir_intrinsic_load_tess_level_outer:
return lower_tes_load(b, intr);
case nir_intrinsic_load_patch_vertices_in:
return poly_tes_patch_vertices_in(b, nir_load_tess_param_buffer_poly(b));
default:
return NULL;
}
}
static bool
lower_tes(nir_builder *b, nir_intrinsic_instr *intr, void *data)
{
b->cursor = nir_before_instr(&intr->instr);
nir_def *repl = lower_tes_impl(b, intr, data);
if (repl) {
nir_def_replace(&intr->def, repl);
return true;
} else {
return false;
}
}
static bool
lower_tes_indexing(nir_builder *b, nir_intrinsic_instr *intr, void *data)
{
if (intr->intrinsic != nir_intrinsic_load_vertex_id)
return false;
b->cursor = nir_before_instr(&intr->instr);
nir_def *p = nir_load_tess_param_buffer_poly(b);
nir_def *id = nir_channel(b, nir_load_global_invocation_id(b, 32), 0);
nir_def_replace(&intr->def, poly_load_tes_index(b, p, id));
return true;
}
bool
poly_nir_lower_tes(nir_shader *tes, bool to_hw_vs)
{
nir_lower_tess_coord_z(
tes, tes->info.tess._primitive_mode == TESS_PRIMITIVE_TRIANGLES);
nir_shader_intrinsics_pass(tes, lower_tes, nir_metadata_control_flow, NULL);
/* Points mode renders as points, make sure we write point size for the HW */
if (tes->info.tess.point_mode && to_hw_vs) {
nir_lower_default_point_size(tes);
}
if (to_hw_vs) {
/* We lower to a HW VS, so update the shader info so the compiler does the
* right thing.
*/
tes->info.stage = MESA_SHADER_VERTEX;
memset(&tes->info.vs, 0, sizeof(tes->info.vs));
tes->info.vs.tes_poly = true;
} else {
/* If we're running as a compute shader, we need to load from the index
* buffer manually. Fortunately, this doesn't require a shader key:
* tess-as-compute always use U32 index buffers.
*/
nir_shader_intrinsics_pass(tes, lower_tes_indexing,
nir_metadata_control_flow, NULL);
}
nir_lower_idiv(tes, &(nir_lower_idiv_options){.allow_fp16 = true});
return nir_progress(true, nir_shader_get_entrypoint(tes), nir_metadata_none);
}
+108
View File
@@ -0,0 +1,108 @@
/*
* Copyright 2024 Valve Corporation
* SPDX-License-Identifier: MIT
*/
#pragma once
#include "compiler/libcl/libcl.h"
enum poly_tess_partitioning {
POLY_TESS_PARTITIONING_FRACTIONAL_ODD,
POLY_TESS_PARTITIONING_FRACTIONAL_EVEN,
POLY_TESS_PARTITIONING_INTEGER,
};
enum poly_tess_mode {
/* Do not actually tessellate, just write the index counts */
POLY_TESS_MODE_COUNT,
/* Tessellate using the count buffers to allocate indices */
POLY_TESS_MODE_WITH_COUNTS,
};
struct poly_tess_point {
uint32_t u;
uint32_t v;
};
static_assert(sizeof(struct poly_tess_point) == 8);
struct poly_tess_args {
/* Heap to allocate tessellator outputs in */
DEVICE(struct poly_heap) heap;
/* Patch coordinate buffer, indexed as:
*
* coord_allocs[patch_ID] + vertex_in_patch
*/
DEVICE(struct poly_tess_point) patch_coord_buffer;
/* Per-patch index within the heap for the tess coords, written by the
* tessellator based on the allocated memory.
*/
DEVICE(uint32_t) coord_allocs;
/* Space for output draws from the tessellator. API draw calls. */
DEVICE(uint32_t) out_draws;
/* Tessellation control shader output buffer. */
DEVICE(float) tcs_buffer;
/* Count buffer. # of indices per patch written here, then prefix summed. */
DEVICE(uint32_t) counts;
/* Allocated index buffer for all patches, if we're prefix summing counts */
DEVICE(uint32_t) index_buffer;
/* Address of the tess eval invocation counter for implementing pipeline
* statistics, if active. Zero if inactive. Incremented by tessellator.
*/
DEVICE(uint32_t) statistic;
/* When geom+tess used together, the buffer containing TES outputs (executed
* as a hardware compute shader).
*/
uint64_t tes_buffer;
/* Bitfield of TCS per-vertex outputs */
uint64_t tcs_per_vertex_outputs;
/* Default tess levels used in OpenGL when there is no TCS in the pipeline.
* Unused in Vulkan and OpenGL ES.
*/
float tess_level_outer_default[4];
float tess_level_inner_default[2];
/* Number of vertices in the input patch */
uint32_t input_patch_size;
/* Number of vertices in the TCS output patch */
uint32_t output_patch_size;
/* Number of patch constants written by TCS */
uint32_t tcs_patch_constants;
/* Number of input patches per instance of the VS/TCS */
uint32_t patches_per_instance;
/* Stride between tessellation facotrs in the TCS output buffer. */
uint32_t tcs_stride_el;
/* Number of patches being tessellated */
uint32_t nr_patches;
/* Partitioning and points mode. These affect per-patch setup code but not
* the hot tessellation loop so we make them dynamic to reduce tessellator
* variants.
*/
enum poly_tess_partitioning partitioning;
uint32_t points_mode;
uint32_t isolines;
/* When fed into a geometry shader, triangles should be counter-clockwise.
* The tessellator always produces clockwise triangles, but we can swap
* dynamically in the TES.
*/
uint32_t ccw;
} PACKED;
static_assert(sizeof(struct poly_tess_args) == 36 * 4);