Files
third_party_mesa3d/src/asahi/lib/shaders/tessellator.cl
2024-07-26 18:40:47 +00:00

1749 lines
65 KiB
C

/*
Copyright (c) Microsoft Corporation
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in
all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
*/
#include "geometry.h"
#include "tessellator.h"
#include <agx_pack.h>
#if 0
#include <math.h>
#include <stdbool.h>
#include <stdint.h>
#include <string.h>
#include "util/macros.h"
#define min(x, y) (x < y ? x : y)
#define max(x, y) (x > y ? x : y)
#define clz(x) (x ? __builtin_clz(x) : (8 * sizeof(x)))
#define clamp(x, y, z) (x < y ? y : x > z ? z : x)
#define align(x, y) ALIGN_POT(x, y)
#else
#define assert(x)
#endif
#define LIBAGX_TESS_MIN_ISOLINE_DENSITY_TESSELLATION_FACTOR 1.0f
#define LIBAGX_TESS_MAX_ISOLINE_DENSITY_TESSELLATION_FACTOR 64.0f
typedef unsigned int FXP; // fixed point number
enum {
U = 0, // points on a tri patch
V = 1,
};
enum {
Ueq0 = 0, // edges on a tri patch
Veq0 = 1,
Weq0 = 2,
};
enum {
Ueq1 = 2, // edges on a quad patch: Ueq0, Veq0, Ueq1, Veq1
Veq1 = 3,
};
#define QUAD_AXES 2
#define QUAD_EDGES 4
#define TRI_EDGES 3
// The interior can just use a simpler stitch.
typedef enum DIAGONALS {
DIAGONALS_INSIDE_TO_OUTSIDE,
DIAGONALS_INSIDE_TO_OUTSIDE_EXCEPT_MIDDLE,
DIAGONALS_MIRRORED
} DIAGONALS;
typedef struct TESS_FACTOR_CONTEXT {
FXP fxpInvNumSegmentsOnFloorTessFactor;
FXP fxpInvNumSegmentsOnCeilTessFactor;
FXP fxpHalfTessFactorFraction;
int numHalfTessFactorPoints;
int splitPointOnFloorHalfTessFactor;
} TESS_FACTOR_CONTEXT;
struct INDEX_PATCH_CONTEXT {
int insidePointIndexDeltaToRealValue;
int insidePointIndexBadValue;
int insidePointIndexReplacementValue;
int outsidePointIndexPatchBase;
int outsidePointIndexDeltaToRealValue;
int outsidePointIndexBadValue;
int outsidePointIndexReplacementValue;
};
struct INDEX_PATCH_CONTEXT2 {
int baseIndexToInvert;
int indexInversionEndPoint;
int cornerCaseBadValue;
int cornerCaseReplacementValue;
};
struct CHWTessellator {
enum libagx_tess_output_primitive outputPrimitive;
enum libagx_tess_mode mode;
uint index_bias;
// array where we will store u/v's for the points we generate
global struct libagx_tess_point *Point;
// array where we will store index topology
global void *Index;
// A second index patch we have to do handles the leftover strip of quads in
// the middle of an odd quad patch after finishing all the concentric rings.
// This also handles the leftover strip of points in the middle of an even
// quad patch, when stitching the row of triangles up the left side (V major
// quad) or bottom (U major quad) of the inner ring
bool bUsingPatchedIndices;
bool bUsingPatchedIndices2;
struct INDEX_PATCH_CONTEXT IndexPatchCtx;
struct INDEX_PATCH_CONTEXT2 IndexPatchCtx2;
};
#define FXP_INTEGER_BITS 15
#define FXP_FRACTION_BITS 16
#define FXP_FRACTION_MASK 0x0000ffff
#define FXP_INTEGER_MASK 0x7fff0000
#define FXP_ONE (1 << FXP_FRACTION_BITS)
#define FXP_ONE_THIRD 0x00005555
#define FXP_TWO_THIRDS 0x0000aaaa
#define FXP_ONE_HALF 0x00008000
static global float *
tess_factors(constant struct libagx_tess_args *p, uint patch)
{
return p->tcs_buffer + (patch * p->tcs_stride_el);
}
static inline uint
libagx_heap_alloc(global struct agx_geometry_state *heap, uint size_B)
{
// TODO: drop align to 4 I think
return atomic_fetch_add((volatile atomic_uint *)(&heap->heap_bottom),
align(size_B, 8));
}
/*
* Generate an indexed draw for a patch with the computed number of indices.
* This allocates heap memory for the index buffer, returning the allocated
* memory.
*/
static global void *
libagx_draw(constant struct libagx_tess_args *p, enum libagx_tess_mode mode,
bool lines, uint patch, uint count)
{
if (mode == LIBAGX_TESS_MODE_COUNT) {
p->counts[patch] = count;
}
if (mode == LIBAGX_TESS_MODE_VDM) {
uint32_t elsize_B = sizeof(uint16_t);
uint32_t alloc_B = libagx_heap_alloc(p->heap, elsize_B * count);
uint64_t ib = ((uintptr_t)p->heap->heap) + alloc_B;
global uint32_t *desc = p->out_draws + (patch * 6);
agx_pack(&desc[0], INDEX_LIST, cfg) {
cfg.index_buffer_hi = (ib >> 32);
cfg.primitive = lines ? AGX_PRIMITIVE_LINES : AGX_PRIMITIVE_TRIANGLES;
cfg.restart_enable = false;
cfg.index_size = AGX_INDEX_SIZE_U16;
cfg.index_buffer_size_present = true;
cfg.index_buffer_present = true;
cfg.index_count_present = true;
cfg.instance_count_present = true;
cfg.start_present = true;
cfg.unk_1_present = false;
cfg.indirect_buffer_present = false;
cfg.unk_2_present = false;
cfg.block_type = AGX_VDM_BLOCK_TYPE_INDEX_LIST;
}
agx_pack(&desc[1], INDEX_LIST_BUFFER_LO, cfg) {
cfg.buffer_lo = ib & 0xffffffff;
}
agx_pack(&desc[2], INDEX_LIST_COUNT, cfg) {
cfg.count = count;
}
agx_pack(&desc[3], INDEX_LIST_INSTANCES, cfg) {
cfg.count = 1;
}
agx_pack(&desc[4], INDEX_LIST_START, cfg) {
cfg.start = patch * LIBAGX_TES_PATCH_ID_STRIDE;
}
agx_pack(&desc[5], INDEX_LIST_BUFFER_SIZE, cfg) {
cfg.size = align(count * 2, 4);
}
return (global void *)ib;
}
if (mode == LIBAGX_TESS_MODE_WITH_COUNTS) {
/* The index buffer is already allocated, get a pointer inside it.
* p->counts has had an inclusive prefix sum hence the subtraction.
*/
uint offset_el = p->counts[sub_sat(patch, 1u)];
if (patch == 0)
offset_el = 0;
return &p->index_buffer[offset_el];
}
return NULL;
}
static void
libagx_draw_points(private struct CHWTessellator *ctx,
constant struct libagx_tess_args *p, uint patch, uint count)
{
if (ctx->mode == LIBAGX_TESS_MODE_VDM) {
/* Generate a non-indexed draw for points mode tessellation. */
global uint32_t *desc = p->out_draws + (patch * 4);
agx_pack(&desc[0], INDEX_LIST, cfg) {
cfg.index_buffer_hi = 0;
cfg.primitive = AGX_PRIMITIVE_POINTS;
cfg.restart_enable = false;
cfg.index_size = 0;
cfg.index_buffer_size_present = false;
cfg.index_buffer_present = false;
cfg.index_count_present = true;
cfg.instance_count_present = true;
cfg.start_present = true;
cfg.unk_1_present = false;
cfg.indirect_buffer_present = false;
cfg.unk_2_present = false;
cfg.block_type = AGX_VDM_BLOCK_TYPE_INDEX_LIST;
}
agx_pack(&desc[1], INDEX_LIST_COUNT, cfg) {
cfg.count = count;
}
agx_pack(&desc[2], INDEX_LIST_INSTANCES, cfg) {
cfg.count = 1;
}
agx_pack(&desc[3], INDEX_LIST_START, cfg) {
cfg.start = patch * LIBAGX_TES_PATCH_ID_STRIDE;
}
} else {
/* For points mode with a single draw, we need to generate a trivial index
* buffer to stuff in the patch ID in the right place.
*/
global uint32_t *indices = libagx_draw(p, ctx->mode, false, patch, count);
if (ctx->mode == LIBAGX_TESS_MODE_COUNT)
return;
for (int i = 0; i < count; ++i) {
indices[i] = ctx->index_bias + i;
}
}
}
static void
libagx_draw_empty(constant struct libagx_tess_args *p,
enum libagx_tess_mode mode,
enum libagx_tess_output_primitive output_primitive,
uint patch)
{
if (mode == LIBAGX_TESS_MODE_COUNT) {
p->counts[patch] = 0;
} else if (mode == LIBAGX_TESS_MODE_VDM) {
uint32_t words = (output_primitive == LIBAGX_TESS_OUTPUT_POINT) ? 4 : 6;
global uint32_t *desc = p->out_draws + (patch * words);
uint32_t nop_token = AGX_VDM_BLOCK_TYPE_BARRIER << 29;
for (uint32_t i = 0; i < words; ++i) {
desc[i] = nop_token;
}
}
}
/*
* Allocate heap memory for domain points for a patch. The allocation
* is recorded in the coord_allocs[] array, which is in elements.
*/
static global struct libagx_tess_point *
libagx_heap_alloc_points(constant struct libagx_tess_args *p, uint patch,
uint count)
{
/* If we're recording statistics, increment now. The statistic is for
* tessellation evaluation shader invocations, which is equal to the number
* of domain points generated.
*/
if (p->statistic) {
atomic_fetch_add((volatile atomic_uint *)(p->statistic), count);
}
uint32_t elsize_B = sizeof(struct libagx_tess_point);
uint32_t alloc_B = libagx_heap_alloc(p->heap, elsize_B * count);
uint32_t alloc_el = alloc_B / elsize_B;
p->coord_allocs[patch] = alloc_el;
return (global struct libagx_tess_point *)(((uintptr_t)p->heap->heap) +
alloc_B);
}
// Microsoft D3D11 Fixed Function Tessellator Reference - May 7, 2012
// amar.patel@microsoft.com
#define LIBAGX_TESS_MIN_ODD_TESSELLATION_FACTOR 1
#define LIBAGX_TESS_MAX_ODD_TESSELLATION_FACTOR 63
#define LIBAGX_TESS_MIN_EVEN_TESSELLATION_FACTOR 2
#define LIBAGX_TESS_MAX_EVEN_TESSELLATION_FACTOR 64
// 2^(-16), min positive fixed point fraction
#define EPSILON 0.0000152587890625f
#define MIN_ODD_TESSFACTOR_PLUS_HALF_EPSILON \
(LIBAGX_TESS_MIN_ODD_TESSELLATION_FACTOR + EPSILON / 2)
static float clamp_factor(float factor,
enum libagx_tess_partitioning partitioning,
float maxf)
{
float lower = (partitioning == LIBAGX_TESS_PARTITIONING_FRACTIONAL_EVEN)
? LIBAGX_TESS_MIN_EVEN_TESSELLATION_FACTOR
: LIBAGX_TESS_MIN_ODD_TESSELLATION_FACTOR;
float upper = (partitioning == LIBAGX_TESS_PARTITIONING_FRACTIONAL_ODD)
? LIBAGX_TESS_MAX_ODD_TESSELLATION_FACTOR
: LIBAGX_TESS_MAX_EVEN_TESSELLATION_FACTOR;
// If any TessFactor will end up > 1 after floatToFixed conversion later,
// then force the inside TessFactors to be > 1 so there is a picture frame.
if (partitioning == LIBAGX_TESS_PARTITIONING_FRACTIONAL_ODD &&
maxf > MIN_ODD_TESSFACTOR_PLUS_HALF_EPSILON) {
lower = LIBAGX_TESS_MIN_ODD_TESSELLATION_FACTOR + EPSILON;
}
factor = clamp(factor, lower, upper);
if (partitioning == LIBAGX_TESS_PARTITIONING_INTEGER) {
factor = ceil(factor);
}
return factor;
}
static FXP
floatToFixed(const float input)
{
return mad(input, FXP_ONE, 0.5f);
}
static float
fixedToFloat(const FXP input)
{
// Don't need to worry about special cases because the bounds are reasonable.
return ((float)input) / FXP_ONE;
}
static bool
isOdd(const float input)
{
return ((int)input) & 1;
}
static FXP
fxpCeil(const FXP input)
{
if (input & FXP_FRACTION_MASK) {
return (input & FXP_INTEGER_MASK) + FXP_ONE;
}
return input;
}
static FXP
fxpFloor(const FXP input)
{
return (input & FXP_INTEGER_MASK);
}
static int
PatchIndexValue(private struct CHWTessellator *ctx, int index)
{
if (ctx->bUsingPatchedIndices) {
// assumed remapped outide indices are > remapped inside vertices
if (index >= ctx->IndexPatchCtx.outsidePointIndexPatchBase) {
if (index == ctx->IndexPatchCtx.outsidePointIndexBadValue)
return ctx->IndexPatchCtx.outsidePointIndexReplacementValue;
else
return index + ctx->IndexPatchCtx.outsidePointIndexDeltaToRealValue;
} else {
if (index == ctx->IndexPatchCtx.insidePointIndexBadValue)
return ctx->IndexPatchCtx.insidePointIndexReplacementValue;
else
return index + ctx->IndexPatchCtx.insidePointIndexDeltaToRealValue;
}
} else if (ctx->bUsingPatchedIndices2) {
if (index >= ctx->IndexPatchCtx2.baseIndexToInvert) {
if (index == ctx->IndexPatchCtx2.cornerCaseBadValue)
return ctx->IndexPatchCtx2.cornerCaseReplacementValue;
else
return ctx->IndexPatchCtx2.indexInversionEndPoint - index;
} else if (index == ctx->IndexPatchCtx2.cornerCaseBadValue) {
return ctx->IndexPatchCtx2.cornerCaseReplacementValue;
}
}
return index;
}
static void
DefinePoint(global struct libagx_tess_point *out, FXP fxpU, FXP fxpV)
{
out->u = fixedToFloat(fxpU);
out->v = fixedToFloat(fxpV);
}
static void
DefineIndex(private struct CHWTessellator *ctx, int index,
int indexStorageOffset)
{
int patched = PatchIndexValue(ctx, index);
if (ctx->mode == LIBAGX_TESS_MODE_WITH_COUNTS) {
global uint32_t *indices = (global uint32_t *)ctx->Index;
indices[indexStorageOffset] = ctx->index_bias + patched;
} else {
global uint16_t *indices = (global uint16_t *)ctx->Index;
indices[indexStorageOffset] = patched;
}
}
static void
DefineClockwiseTriangle(private struct CHWTessellator *ctx, int index0,
int index1, int index2, int indexStorageBaseOffset)
{
// inputs a clockwise triangle, stores a CW or CCW triangle per state state
bool cw = ctx->outputPrimitive == LIBAGX_TESS_OUTPUT_TRIANGLE_CW;
DefineIndex(ctx, index0, indexStorageBaseOffset);
DefineIndex(ctx, cw ? index1 : index2, indexStorageBaseOffset + 1);
DefineIndex(ctx, cw ? index2 : index1, indexStorageBaseOffset + 2);
}
static uint32_t
RemoveMSB(uint32_t val)
{
uint32_t bit = val ? (1 << (31 - clz(val))) : 0;
return val & ~bit;
}
static int
NumPointsForTessFactor(bool odd, FXP fxpTessFactor)
{
// Add epsilon for rounding and add 1 for odd
FXP f = fxpTessFactor + (odd ? (FXP_ONE + 1) : 1);
int r = fxpCeil(f / 2) >> (FXP_FRACTION_BITS - 1);
return odd ? r : r + 1;
}
static void
ComputeTessFactorCtx(bool odd, FXP fxpTessFactor,
private TESS_FACTOR_CONTEXT *TessFactorCtx)
{
// fxpHalfTessFactor == 1/2 if TessFactor is 1,
// but we're pretending we are even.
FXP fxpHalfTessFactor = (fxpTessFactor + 1 /*round*/) / 2;
if (odd || (fxpHalfTessFactor == FXP_ONE_HALF)) {
fxpHalfTessFactor += FXP_ONE_HALF;
}
FXP fxpFloorHalfTessFactor = fxpFloor(fxpHalfTessFactor);
FXP fxpCeilHalfTessFactor = fxpCeil(fxpHalfTessFactor);
TessFactorCtx->fxpHalfTessFactorFraction = fxpHalfTessFactor - fxpFloorHalfTessFactor;
TessFactorCtx->numHalfTessFactorPoints =
(fxpCeilHalfTessFactor >> FXP_FRACTION_BITS); // for EVEN, we don't include the point always
// fixed at the midpoint of the TessFactor
if (fxpCeilHalfTessFactor == fxpFloorHalfTessFactor) {
TessFactorCtx->splitPointOnFloorHalfTessFactor =
/*pick value to cause this to be ignored*/ TessFactorCtx->numHalfTessFactorPoints + 1;
} else if (odd) {
if (fxpFloorHalfTessFactor == FXP_ONE) {
TessFactorCtx->splitPointOnFloorHalfTessFactor = 0;
} else {
TessFactorCtx->splitPointOnFloorHalfTessFactor =
(RemoveMSB((fxpFloorHalfTessFactor >> FXP_FRACTION_BITS) - 1) << 1) + 1;
}
} else {
TessFactorCtx->splitPointOnFloorHalfTessFactor =
(RemoveMSB(fxpFloorHalfTessFactor >> FXP_FRACTION_BITS) << 1) + 1;
}
int numFloorSegments = (fxpFloorHalfTessFactor * 2) >> FXP_FRACTION_BITS;
int numCeilSegments = (fxpCeilHalfTessFactor * 2) >> FXP_FRACTION_BITS;
if (odd) {
numFloorSegments -= 1;
numCeilSegments -= 1;
}
TessFactorCtx->fxpInvNumSegmentsOnFloorTessFactor =
floatToFixed(1.0f / (float)numFloorSegments);
TessFactorCtx->fxpInvNumSegmentsOnCeilTessFactor =
floatToFixed(1.0f / (float)numCeilSegments);
}
static FXP
PlacePointIn1D(private const TESS_FACTOR_CONTEXT *TessFactorCtx, bool odd,
int point)
{
bool bFlip = point >= TessFactorCtx->numHalfTessFactorPoints;
if (bFlip) {
point = (TessFactorCtx->numHalfTessFactorPoints << 1) - point - odd;
}
// special casing middle since 16 bit fixed math below can't reproduce 0.5 exactly
if (point == TessFactorCtx->numHalfTessFactorPoints)
return FXP_ONE_HALF;
unsigned int indexOnCeilHalfTessFactor = point;
unsigned int indexOnFloorHalfTessFactor = indexOnCeilHalfTessFactor;
if (point > TessFactorCtx->splitPointOnFloorHalfTessFactor) {
indexOnFloorHalfTessFactor -= 1;
}
// For the fixed point multiplies below, we know the results are <= 16 bits
// because the locations on the halfTessFactor are <= half the number of
// segments for the total TessFactor. So a number divided by a number that
// is at least twice as big will give a result no bigger than 0.5 (which in
// fixed point is 16 bits in our case)
FXP fxpLocationOnFloorHalfTessFactor =
indexOnFloorHalfTessFactor * TessFactorCtx->fxpInvNumSegmentsOnFloorTessFactor;
FXP fxpLocationOnCeilHalfTessFactor =
indexOnCeilHalfTessFactor * TessFactorCtx->fxpInvNumSegmentsOnCeilTessFactor;
// Since we know the numbers calculated above are <= fixed point 0.5, and the
// equation below is just lerping between two values <= fixed point 0.5
// (0x00008000), then we know that the final result before shifting by 16 bits
// is no larger than 0x80000000. Once we shift that down by 16, we get the
// result of lerping 2 numbers <= 0.5, which is obviously at most 0.5
// (0x00008000)
FXP fxpLocation =
fxpLocationOnFloorHalfTessFactor * (FXP_ONE - TessFactorCtx->fxpHalfTessFactorFraction) +
fxpLocationOnCeilHalfTessFactor * (TessFactorCtx->fxpHalfTessFactorFraction);
fxpLocation = (fxpLocation + FXP_ONE_HALF /*round*/) >> FXP_FRACTION_BITS; // get back to n.16
if (bFlip) {
fxpLocation = FXP_ONE - fxpLocation;
}
return fxpLocation;
}
static void
StitchRegular(private struct CHWTessellator *ctx, bool bTrapezoid,
DIAGONALS diagonals, int baseIndexOffset, int numInsideEdgePoints,
int insideEdgePointBaseOffset, int outsideEdgePointBaseOffset)
{
int insidePoint = insideEdgePointBaseOffset;
int outsidePoint = outsideEdgePointBaseOffset;
if (bTrapezoid) {
DefineClockwiseTriangle(ctx, outsidePoint, outsidePoint + 1, insidePoint, baseIndexOffset);
baseIndexOffset += 3;
outsidePoint++;
}
int p;
switch (diagonals) {
case DIAGONALS_INSIDE_TO_OUTSIDE:
// Diagonals pointing from inside edge forward towards outside edge
for (p = 0; p < numInsideEdgePoints - 1; p++) {
DefineClockwiseTriangle(ctx, insidePoint, outsidePoint, outsidePoint + 1, baseIndexOffset);
baseIndexOffset += 3;
DefineClockwiseTriangle(ctx, insidePoint, outsidePoint + 1, insidePoint + 1,
baseIndexOffset);
baseIndexOffset += 3;
insidePoint++;
outsidePoint++;
}
break;
case DIAGONALS_INSIDE_TO_OUTSIDE_EXCEPT_MIDDLE: // Assumes ODD tessellation
// Diagonals pointing from outside edge forward towards inside edge
// First half
for (p = 0; p < numInsideEdgePoints / 2 - 1; p++) {
DefineClockwiseTriangle(ctx, outsidePoint, outsidePoint + 1, insidePoint, baseIndexOffset);
baseIndexOffset += 3;
DefineClockwiseTriangle(ctx, insidePoint, outsidePoint + 1, insidePoint + 1,
baseIndexOffset);
baseIndexOffset += 3;
insidePoint++;
outsidePoint++;
}
// Middle
DefineClockwiseTriangle(ctx, outsidePoint, insidePoint + 1, insidePoint, baseIndexOffset);
baseIndexOffset += 3;
DefineClockwiseTriangle(ctx, outsidePoint, outsidePoint + 1, insidePoint + 1,
baseIndexOffset);
baseIndexOffset += 3;
insidePoint++;
outsidePoint++;
p += 2;
// Second half
for (; p < numInsideEdgePoints; p++) {
DefineClockwiseTriangle(ctx, outsidePoint, outsidePoint + 1, insidePoint, baseIndexOffset);
baseIndexOffset += 3;
DefineClockwiseTriangle(ctx, insidePoint, outsidePoint + 1, insidePoint + 1,
baseIndexOffset);
baseIndexOffset += 3;
insidePoint++;
outsidePoint++;
}
break;
case DIAGONALS_MIRRORED:
// First half, diagonals pointing from outside of outside edge to inside of
// inside edge
for (p = 0; p < numInsideEdgePoints / 2; p++) {
DefineClockwiseTriangle(ctx, outsidePoint, insidePoint + 1, insidePoint, baseIndexOffset);
baseIndexOffset += 3;
DefineClockwiseTriangle(ctx, outsidePoint, outsidePoint + 1, insidePoint + 1,
baseIndexOffset);
baseIndexOffset += 3;
insidePoint++;
outsidePoint++;
}
// Second half, diagonals pointing from inside of inside edge to outside of
// outside edge
for (; p < numInsideEdgePoints - 1; p++) {
DefineClockwiseTriangle(ctx, insidePoint, outsidePoint, outsidePoint + 1, baseIndexOffset);
baseIndexOffset += 3;
DefineClockwiseTriangle(ctx, insidePoint, outsidePoint + 1, insidePoint + 1,
baseIndexOffset);
baseIndexOffset += 3;
insidePoint++;
outsidePoint++;
}
break;
}
if (bTrapezoid) {
DefineClockwiseTriangle(ctx, outsidePoint, outsidePoint + 1, insidePoint, baseIndexOffset);
baseIndexOffset += 3;
}
}
// loop_start and loop_end give optimal loop bounds for
// the stitching algorithm further below, for any given halfTssFactor. There
// is probably a better way to encode this...
//
// Return the FIRST entry in finalPointPositionTable awhich is less than
// halfTessFactor, except entry 0 and 1 which are set up to skip the loop.
static int
loop_start(int N)
{
if (N < 2)
return 1;
else if (N == 2)
return 17;
else if (N < 5)
return 9;
else if (N < 9)
return 5;
else if (N < 17)
return 3;
else
return 2;
}
// Return the LAST entry in finalPointPositionTable[] which is less than
// halfTessFactor, except entry 0 and 1 which are set up to skip the loop.
static int
loop_end(int N)
{
if (N < 2)
return 0;
else if (N < 4)
return 17;
else if (N < 8)
return 25;
else if (N < 16)
return 29;
else if (N < 32)
return 31;
else
return 32;
}
// Tables to assist in the stitching of 2 rows of points having arbitrary
// TessFactors. The stitching order is governed by Ruler Function vertex
// split ordering (see external documentation).
//
// The contents of the finalPointPositionTable are where vertex i [0..33]
// ends up on the half-edge at the max tessellation amount given
// ruler-function split order. Recall the other half of an edge is mirrored,
// so we only need to deal with one half. This table is used to decide when
// to advance a point on the interior or exterior. It supports odd TessFactor
// up to 65 and even TessFactor up to 64.
/* TODO: Is this actually faster than a LUT? */
static uint32_t
finalPointPositionTable(uint32_t x)
{
if (x == 0)
return 0;
if (x == 1)
return 0x20;
uint32_t shift;
if ((x & 1) == 0) {
shift = 1;
} else if ((x & 3) == 3) {
shift = 2;
} else if ((x & 7) == 5) {
shift = 3;
} else if (x != 17) {
shift = 4;
} else {
shift = 5;
}
// SWAR vectorized right-shift of (0x20, x)
// We're calculating `min(0xf, 0x20 >> shift) + (x >> shift)`.
uint32_t items_to_shift = x | (0x20 << 16);
uint32_t shifted = items_to_shift >> shift;
uint32_t bias = min(0xfu, shifted >> 16);
return bias + (shifted & 0xffff);
}
static void
StitchTransition(private struct CHWTessellator *ctx, int baseIndexOffset,
int insideEdgePointBaseOffset,
int insideNumHalfTessFactorPoints,
bool insideEdgeTessFactorOdd, int outsideEdgePointBaseOffset,
int outsideNumHalfTessFactorPoints, bool outsideTessFactorOdd)
{
if (insideEdgeTessFactorOdd) {
insideNumHalfTessFactorPoints -= 1;
}
if (outsideTessFactorOdd) {
outsideNumHalfTessFactorPoints -= 1;
}
// Walk first half
int outsidePoint = outsideEdgePointBaseOffset;
int insidePoint = insideEdgePointBaseOffset;
// iStart,iEnd are a small optimization so the loop below doesn't have to go
// from 0 up to 31
int iStart = min(loop_start(insideNumHalfTessFactorPoints),
loop_start(outsideNumHalfTessFactorPoints));
int iEnd = loop_end(
max(insideNumHalfTessFactorPoints, outsideNumHalfTessFactorPoints));
// since we don't start the loop at 0 below, we need a special case.
if (0 < outsideNumHalfTessFactorPoints) {
// Advance outside
DefineClockwiseTriangle(ctx, outsidePoint, outsidePoint + 1, insidePoint,
baseIndexOffset);
baseIndexOffset += 3;
outsidePoint++;
}
for (int i = iStart; i <= iEnd; i++) {
int bound = finalPointPositionTable(i);
if (bound < insideNumHalfTessFactorPoints) {
// Advance inside
DefineClockwiseTriangle(ctx, insidePoint, outsidePoint,
insidePoint + 1, baseIndexOffset);
baseIndexOffset += 3;
insidePoint++;
}
if (bound < outsideNumHalfTessFactorPoints) {
// Advance outside
DefineClockwiseTriangle(ctx, outsidePoint, outsidePoint + 1,
insidePoint, baseIndexOffset);
baseIndexOffset += 3;
outsidePoint++;
}
}
if ((insideEdgeTessFactorOdd != outsideTessFactorOdd) ||
insideEdgeTessFactorOdd) {
if (insideEdgeTessFactorOdd == outsideTessFactorOdd) {
// Quad in the middle
DefineClockwiseTriangle(ctx, insidePoint, outsidePoint,
insidePoint + 1, baseIndexOffset);
baseIndexOffset += 3;
DefineClockwiseTriangle(ctx, insidePoint + 1, outsidePoint,
outsidePoint + 1, baseIndexOffset);
baseIndexOffset += 3;
insidePoint++;
outsidePoint++;
} else if (!insideEdgeTessFactorOdd) {
// Triangle pointing inside
DefineClockwiseTriangle(ctx, insidePoint, outsidePoint,
outsidePoint + 1, baseIndexOffset);
baseIndexOffset += 3;
outsidePoint++;
} else {
// Triangle pointing outside
DefineClockwiseTriangle(ctx, insidePoint, outsidePoint,
insidePoint + 1, baseIndexOffset);
baseIndexOffset += 3;
insidePoint++;
}
}
// Walk second half.
for (int i = iEnd; i >= iStart; i--) {
int bound = finalPointPositionTable(i);
if (bound < outsideNumHalfTessFactorPoints) {
// Advance outside
DefineClockwiseTriangle(ctx, outsidePoint, outsidePoint + 1,
insidePoint, baseIndexOffset);
baseIndexOffset += 3;
outsidePoint++;
}
if (bound < insideNumHalfTessFactorPoints) {
// Advance inside
DefineClockwiseTriangle(ctx, insidePoint, outsidePoint,
insidePoint + 1, baseIndexOffset);
baseIndexOffset += 3;
insidePoint++;
}
}
// Below case is not needed if we didn't optimize loop above and made it run
// from 31 down to 0.
if (0 < outsideNumHalfTessFactorPoints) {
DefineClockwiseTriangle(ctx, outsidePoint, outsidePoint + 1, insidePoint,
baseIndexOffset);
baseIndexOffset += 3;
outsidePoint++;
}
}
void
libagx_tess_isoline(constant struct libagx_tess_args *p,
enum libagx_tess_mode mode,
enum libagx_tess_partitioning partitioning,
enum libagx_tess_output_primitive output_primitive,
uint patch)
{
bool lineDensityOdd;
bool lineDetailOdd;
TESS_FACTOR_CONTEXT lineDensityTessFactorCtx;
TESS_FACTOR_CONTEXT lineDetailTessFactorCtx;
global float *factors = tess_factors(p, patch);
float TessFactor_V_LineDensity = factors[0];
float TessFactor_U_LineDetail = factors[1];
// Is the patch culled? NaN will pass.
if (!(TessFactor_V_LineDensity > 0) || !(TessFactor_U_LineDetail > 0)) {
libagx_draw_empty(p, mode, output_primitive, patch);
return;
}
// Clamp edge TessFactors
TessFactor_V_LineDensity =
clamp(TessFactor_V_LineDensity,
LIBAGX_TESS_MIN_ISOLINE_DENSITY_TESSELLATION_FACTOR,
LIBAGX_TESS_MAX_ISOLINE_DENSITY_TESSELLATION_FACTOR);
TessFactor_U_LineDetail =
clamp_factor(TessFactor_U_LineDetail, partitioning, 0);
// Process tessFactors
if (partitioning == LIBAGX_TESS_PARTITIONING_INTEGER) {
lineDetailOdd = isOdd(TessFactor_U_LineDetail);
} else {
lineDetailOdd = (partitioning == LIBAGX_TESS_PARTITIONING_FRACTIONAL_ODD);
}
FXP fxpTessFactor_U_LineDetail = floatToFixed(TessFactor_U_LineDetail);
ComputeTessFactorCtx(lineDetailOdd, fxpTessFactor_U_LineDetail,
&lineDetailTessFactorCtx);
int numPointsPerLine =
NumPointsForTessFactor(lineDetailOdd, fxpTessFactor_U_LineDetail);
TessFactor_V_LineDensity = ceil(TessFactor_V_LineDensity);
lineDensityOdd = isOdd(TessFactor_V_LineDensity);
FXP fxpTessFactor_V_LineDensity = floatToFixed(TessFactor_V_LineDensity);
ComputeTessFactorCtx(lineDensityOdd, fxpTessFactor_V_LineDensity,
&lineDensityTessFactorCtx);
// don't draw last line at V == 1.
int numLines =
NumPointsForTessFactor(lineDensityOdd, fxpTessFactor_V_LineDensity) - 1;
/* Points */
uint num_points = numPointsPerLine * numLines;
if (mode != LIBAGX_TESS_MODE_COUNT) {
global struct libagx_tess_point *points =
libagx_heap_alloc_points(p, patch, num_points);
for (int line = 0, pointOffset = 0; line < numLines; line++) {
FXP fxpV =
PlacePointIn1D(&lineDensityTessFactorCtx, lineDensityOdd, line);
for (int point = 0; point < numPointsPerLine; point++) {
FXP fxpU =
PlacePointIn1D(&lineDetailTessFactorCtx, lineDetailOdd, point);
DefinePoint(&points[pointOffset++], fxpU, fxpV);
}
}
}
struct CHWTessellator ctx;
ctx.mode = mode;
ctx.index_bias = patch * LIBAGX_TES_PATCH_ID_STRIDE;
/* Connectivity */
if (output_primitive != LIBAGX_TESS_OUTPUT_POINT) {
uint num_indices = numLines * (numPointsPerLine - 1) * 2;
ctx.Index = libagx_draw(p, mode, true, patch, num_indices);
if (mode == LIBAGX_TESS_MODE_COUNT)
return;
for (int line = 0, pointOffset = 0, indexOffset = 0; line < numLines;
line++) {
pointOffset++;
for (int point = 1; point < numPointsPerLine; point++) {
DefineIndex(&ctx, pointOffset - 1, indexOffset++);
DefineIndex(&ctx, pointOffset, indexOffset++);
pointOffset++;
}
}
} else {
libagx_draw_points(&ctx, p, patch, num_points);
}
}
void
libagx_tess_tri(constant struct libagx_tess_args *p, enum libagx_tess_mode mode,
enum libagx_tess_partitioning partitioning,
enum libagx_tess_output_primitive output_primitive, uint patch)
{
global float *factors = tess_factors(p, patch);
float tessFactor_Ueq0 = factors[0];
float tessFactor_Veq0 = factors[1];
float tessFactor_Weq0 = factors[2];
float insideTessFactor_f = factors[4];
struct CHWTessellator ctx;
ctx.outputPrimitive = output_primitive;
ctx.Point = NULL;
ctx.Index = NULL;
ctx.mode = mode;
ctx.index_bias = patch * LIBAGX_TES_PATCH_ID_STRIDE;
ctx.bUsingPatchedIndices = false;
ctx.bUsingPatchedIndices2 = false;
// Is the patch culled? NaN will pass.
if (!(tessFactor_Ueq0 > 0) || !(tessFactor_Veq0 > 0) ||
!(tessFactor_Weq0 > 0)) {
libagx_draw_empty(p, mode, output_primitive, patch);
return;
}
FXP outsideTessFactor[TRI_EDGES];
FXP insideTessFactor;
bool outsideTessFactorOdd[TRI_EDGES];
bool insideTessFactorOdd;
TESS_FACTOR_CONTEXT outsideTessFactorCtx[TRI_EDGES];
TESS_FACTOR_CONTEXT insideTessFactorCtx;
// Stuff below is just specific to the traversal order
// this code happens to use to generate points/lines
int numPointsForOutsideEdge[TRI_EDGES];
int numPointsForInsideTessFactor;
int insideEdgePointBaseOffset;
// Clamp TessFactors
tessFactor_Ueq0 = clamp_factor(tessFactor_Ueq0, partitioning, 0);
tessFactor_Veq0 = clamp_factor(tessFactor_Veq0, partitioning, 0);
tessFactor_Weq0 = clamp_factor(tessFactor_Weq0, partitioning, 0);
float maxf = max(max(tessFactor_Ueq0, tessFactor_Veq0), tessFactor_Weq0);
insideTessFactor_f = clamp_factor(insideTessFactor_f, partitioning, maxf);
// Note the above clamps map NaN to the lower bound
// Process tessFactors
float outsideTessFactor_f[TRI_EDGES] = {tessFactor_Ueq0, tessFactor_Veq0,
tessFactor_Weq0};
if (partitioning == LIBAGX_TESS_PARTITIONING_INTEGER) {
for (int edge = 0; edge < TRI_EDGES; edge++) {
outsideTessFactorOdd[edge] = isOdd(outsideTessFactor_f[edge]);
}
insideTessFactorOdd =
isOdd(insideTessFactor_f) && (1.0f != insideTessFactor_f);
} else {
bool odd = (partitioning == LIBAGX_TESS_PARTITIONING_FRACTIONAL_ODD);
for (int edge = 0; edge < TRI_EDGES; edge++) {
outsideTessFactorOdd[edge] = odd;
}
insideTessFactorOdd = odd;
}
// Save fixed point TessFactors
for (int edge = 0; edge < TRI_EDGES; edge++) {
outsideTessFactor[edge] = floatToFixed(outsideTessFactor_f[edge]);
}
insideTessFactor = floatToFixed(insideTessFactor_f);
if (partitioning != LIBAGX_TESS_PARTITIONING_FRACTIONAL_EVEN) {
// Special case if all TessFactors are 1
if ((FXP_ONE == insideTessFactor) &&
(FXP_ONE == outsideTessFactor[Ueq0]) &&
(FXP_ONE == outsideTessFactor[Veq0]) &&
(FXP_ONE == outsideTessFactor[Weq0])) {
/* Just do minimum tess factor */
if (mode == LIBAGX_TESS_MODE_COUNT) {
p->counts[patch] = 3;
return;
}
global struct libagx_tess_point *points =
libagx_heap_alloc_points(p, patch, 3);
DefinePoint(&points[0], 0,
FXP_ONE); // V=1 (beginning of Ueq0 edge VW)
DefinePoint(&points[1], 0, 0); // W=1 (beginning of Veq0 edge WU)
DefinePoint(&points[2], FXP_ONE,
0); // U=1 (beginning of Weq0 edge UV)
if (output_primitive != LIBAGX_TESS_OUTPUT_POINT) {
ctx.Index = libagx_draw(p, mode, false, patch, 3);
DefineClockwiseTriangle(&ctx, 0, 1, 2,
/*indexStorageBaseOffset*/ 0);
} else {
libagx_draw_points(&ctx, p, patch, 3);
}
return;
}
}
// Compute per-TessFactor metadata
for (int edge = 0; edge < TRI_EDGES; edge++) {
ComputeTessFactorCtx(outsideTessFactorOdd[edge], outsideTessFactor[edge],
&outsideTessFactorCtx[edge]);
}
ComputeTessFactorCtx(insideTessFactorOdd, insideTessFactor,
&insideTessFactorCtx);
// Compute some initial data.
int NumPoints = 0;
// outside edge offsets and storage
for (int edge = 0; edge < TRI_EDGES; edge++) {
numPointsForOutsideEdge[edge] = NumPointsForTessFactor(
outsideTessFactorOdd[edge], outsideTessFactor[edge]);
NumPoints += numPointsForOutsideEdge[edge];
}
NumPoints -= 3;
// inside edge offsets
numPointsForInsideTessFactor =
NumPointsForTessFactor(insideTessFactorOdd, insideTessFactor);
{
int pointCountMin = insideTessFactorOdd ? 4 : 3;
// max() allows degenerate transition regions when inside TessFactor == 1
numPointsForInsideTessFactor =
max(pointCountMin, numPointsForInsideTessFactor);
}
insideEdgePointBaseOffset = NumPoints;
// inside storage, including interior edges above
{
int interiorRings = (numPointsForInsideTessFactor >> 1) - 1;
int even = insideTessFactorOdd ? 0 : 1;
NumPoints += TRI_EDGES * (interiorRings * (interiorRings + even)) + even;
}
/* GENERATE POINTS */
if (mode != LIBAGX_TESS_MODE_COUNT) {
ctx.Point = libagx_heap_alloc_points(p, patch, NumPoints);
// Generate exterior ring edge points, clockwise starting from point V
// (VW, the U==0 edge)
int pointOffset = 0;
for (int edge = 0; edge < TRI_EDGES; edge++) {
int odd = edge & 0x1;
int endPoint = numPointsForOutsideEdge[edge] - 1;
// don't include end, since next edge starts with it.
for (int p = 0; p < endPoint; p++, pointOffset++) {
// whether to reverse point order given we are defining V or U (W
// implicit): edge0, VW, has V decreasing, so reverse 1D points
// below edge1, WU, has U increasing, so don't reverse 1D points
// below edge2, UV, has U decreasing, so reverse 1D points below
int q = odd ? p : endPoint - p;
FXP fxpParam = PlacePointIn1D(&outsideTessFactorCtx[edge],
outsideTessFactorOdd[edge], q);
if (edge == 0) {
DefinePoint(&ctx.Point[pointOffset], 0, fxpParam);
} else {
DefinePoint(&ctx.Point[pointOffset], fxpParam,
(edge == 2) ? FXP_ONE - fxpParam : 0);
}
}
}
// Generate interior ring points, clockwise spiralling in
int numRings = (numPointsForInsideTessFactor >> 1);
for (int ring = 1; ring < numRings; ring++) {
int startPoint = ring;
int endPoint = numPointsForInsideTessFactor - 1 - startPoint;
int perpendicularAxisPoint = startPoint;
FXP fxpPerpParam = PlacePointIn1D(
&insideTessFactorCtx, insideTessFactorOdd, perpendicularAxisPoint);
// Map location to the right size in
// barycentric space. We know this fixed
// point math won't over/underflow
fxpPerpParam *= FXP_TWO_THIRDS;
fxpPerpParam = (fxpPerpParam + FXP_ONE_HALF /*round*/) >>
FXP_FRACTION_BITS; // get back to n.16
for (int edge = 0; edge < TRI_EDGES; edge++) {
int odd = edge & 0x1;
// don't include end: next edge starts with it.
for (int p = startPoint; p < endPoint; p++, pointOffset++) {
// whether to reverse point given we are defining V or U (W
// implicit): edge0, VW, has V decreasing, so reverse 1D points
// below edge1, WU, has U increasing, so don't reverse 1D points
// below edge2, UV, has U decreasing, so reverse 1D points below
int q = odd ? p : endPoint - (p - startPoint);
FXP fxpParam =
PlacePointIn1D(&insideTessFactorCtx, insideTessFactorOdd, q);
// edge0 VW, has perpendicular parameter U constant
// edge1 WU, has perpendicular parameter V constant
// edge2 UV, has perpendicular parameter W constant
// reciprocal is the rate of change of edge-parallel parameters
// as they are pushed into the triangle
const unsigned int deriv = 2;
// we know this fixed point math won't over/underflow
FXP tmp = fxpParam - (fxpPerpParam + 1 /*round*/) / deriv;
DefinePoint(&ctx.Point[pointOffset],
edge > 0 ? tmp : fxpPerpParam,
edge == 0 ? tmp
: edge == 1 ? fxpPerpParam
: FXP_ONE - tmp - fxpPerpParam);
}
}
}
if (!insideTessFactorOdd) {
// Last point is the point at the center.
DefinePoint(&ctx.Point[pointOffset], FXP_ONE_THIRD, FXP_ONE_THIRD);
}
}
if (output_primitive == LIBAGX_TESS_OUTPUT_POINT) {
libagx_draw_points(&ctx, p, patch, NumPoints);
return;
}
{
// Generate primitives for all the concentric rings, one side at a time
// for each ring +1 is so even tess includes the center point, which we
// want to now
int numRings = ((numPointsForInsideTessFactor + 1) >> 1);
int NumIndices = 0;
{
assert(numRings >= 2 && "invariant");
int OuterPoints = numPointsForOutsideEdge[0] +
numPointsForOutsideEdge[1] +
numPointsForOutsideEdge[2];
int numRings18 = numRings * 18;
NumIndices = ((numRings18 - 27) * numPointsForInsideTessFactor) +
(3 * OuterPoints) - (numRings18 * (numRings - 1)) +
(insideTessFactorOdd ? 3 : 0);
}
// Generate the draw and allocate the index buffer now that we know the size
ctx.Index = libagx_draw(p, mode, false, patch, NumIndices);
if (mode == LIBAGX_TESS_MODE_COUNT)
return;
int insideOffset = insideEdgePointBaseOffset;
int outsideEdgePointBaseOffset = 0;
NumIndices = 0;
for (int ring = 1; ring < numRings; ring++) {
int numPointsForInsideEdge = numPointsForInsideTessFactor - 2 * ring;
int edge0InsidePointBaseOffset = insideOffset;
int edge0OutsidePointBaseOffset = outsideEdgePointBaseOffset;
for (int edge = 0; edge < TRI_EDGES; edge++) {
int outsidePoints = ring == 1 ? numPointsForOutsideEdge[edge]
: (numPointsForInsideEdge + 2);
int numTriangles = numPointsForInsideEdge + outsidePoints - 2;
int insideBaseOffset;
int outsideBaseOffset;
if (edge == 2) {
ctx.IndexPatchCtx.insidePointIndexDeltaToRealValue =
insideOffset;
ctx.IndexPatchCtx.insidePointIndexBadValue =
numPointsForInsideEdge - 1;
ctx.IndexPatchCtx.insidePointIndexReplacementValue =
edge0InsidePointBaseOffset;
ctx.IndexPatchCtx.outsidePointIndexPatchBase =
ctx.IndexPatchCtx.insidePointIndexBadValue +
1; // past inside patched index range
ctx.IndexPatchCtx.outsidePointIndexDeltaToRealValue =
outsideEdgePointBaseOffset -
ctx.IndexPatchCtx.outsidePointIndexPatchBase;
ctx.IndexPatchCtx.outsidePointIndexBadValue =
ctx.IndexPatchCtx.outsidePointIndexPatchBase + outsidePoints -
1;
ctx.IndexPatchCtx.outsidePointIndexReplacementValue =
edge0OutsidePointBaseOffset;
ctx.bUsingPatchedIndices = true;
insideBaseOffset = 0;
outsideBaseOffset = ctx.IndexPatchCtx.outsidePointIndexPatchBase;
} else {
insideBaseOffset = insideOffset;
outsideBaseOffset = outsideEdgePointBaseOffset;
}
if (ring == 1) {
StitchTransition(
&ctx, /*baseIndexOffset: */ NumIndices, insideBaseOffset,
insideTessFactorCtx.numHalfTessFactorPoints,
insideTessFactorOdd, outsideBaseOffset,
outsideTessFactorCtx[edge].numHalfTessFactorPoints,
outsideTessFactorOdd[edge]);
} else {
StitchRegular(&ctx, /*bTrapezoid*/ true, DIAGONALS_MIRRORED,
/*baseIndexOffset: */ NumIndices,
numPointsForInsideEdge, insideBaseOffset,
outsideBaseOffset);
}
if (2 == edge) {
ctx.bUsingPatchedIndices = false;
}
NumIndices += numTriangles * 3;
outsideEdgePointBaseOffset += outsidePoints - 1;
insideOffset += numPointsForInsideEdge - 1;
}
}
if (insideTessFactorOdd) {
// Triangulate center (a single triangle)
DefineClockwiseTriangle(&ctx, outsideEdgePointBaseOffset,
outsideEdgePointBaseOffset + 1,
outsideEdgePointBaseOffset + 2, NumIndices);
NumIndices += 3;
}
}
}
void
libagx_tess_quad(constant struct libagx_tess_args *p,
enum libagx_tess_mode mode,
enum libagx_tess_partitioning partitioning,
enum libagx_tess_output_primitive output_primitive, uint patch)
{
global float *factors = tess_factors(p, patch);
float tessFactor_Ueq0 = factors[0];
float tessFactor_Veq0 = factors[1];
float tessFactor_Ueq1 = factors[2];
float tessFactor_Veq1 = factors[3];
float insideTessFactor_U = factors[4];
float insideTessFactor_V = factors[5];
// TODO: fix designated initializer optimization in NIR
struct CHWTessellator ctx;
ctx.outputPrimitive = output_primitive;
ctx.Point = NULL;
ctx.Index = NULL;
ctx.mode = mode;
ctx.index_bias = patch * LIBAGX_TES_PATCH_ID_STRIDE;
ctx.bUsingPatchedIndices = false;
ctx.bUsingPatchedIndices2 = false;
// Is the patch culled?
if (!(tessFactor_Ueq0 > 0) || // NaN will pass
!(tessFactor_Veq0 > 0) || !(tessFactor_Ueq1 > 0) ||
!(tessFactor_Veq1 > 0)) {
libagx_draw_empty(p, mode, output_primitive, patch);
return;
}
FXP outsideTessFactor[QUAD_EDGES];
FXP insideTessFactor[QUAD_AXES];
bool outsideTessFactorOdd[QUAD_EDGES];
bool insideTessFactorOdd[QUAD_AXES];
TESS_FACTOR_CONTEXT outsideTessFactorCtx[QUAD_EDGES];
TESS_FACTOR_CONTEXT insideTessFactorCtx[QUAD_AXES];
// Stuff below is just specific to the traversal order
// this code happens to use to generate points/lines
int numPointsForOutsideEdge[QUAD_EDGES];
int numPointsForInsideTessFactor[QUAD_AXES];
int insideEdgePointBaseOffset;
// Clamp edge TessFactors
tessFactor_Ueq0 = clamp_factor(tessFactor_Ueq0, partitioning, 0);
tessFactor_Veq0 = clamp_factor(tessFactor_Veq0, partitioning, 0);
tessFactor_Ueq1 = clamp_factor(tessFactor_Ueq1, partitioning, 0);
tessFactor_Veq1 = clamp_factor(tessFactor_Veq1, partitioning, 0);
float maxf = max(max(max(tessFactor_Ueq0, tessFactor_Veq0),
max(tessFactor_Ueq1, tessFactor_Veq1)),
max(insideTessFactor_U, insideTessFactor_V));
insideTessFactor_U = clamp_factor(insideTessFactor_U, partitioning, maxf);
insideTessFactor_V = clamp_factor(insideTessFactor_V, partitioning, maxf);
// Note the above clamps map NaN to lowerBound
// Process tessFactors
float outsideTessFactor_f[QUAD_EDGES] = {tessFactor_Ueq0, tessFactor_Veq0,
tessFactor_Ueq1, tessFactor_Veq1};
float insideTessFactor_f[QUAD_AXES] = {insideTessFactor_U,
insideTessFactor_V};
int edge, axis;
if (partitioning == LIBAGX_TESS_PARTITIONING_INTEGER) {
for (edge = 0; edge < QUAD_EDGES; edge++) {
outsideTessFactorOdd[edge] = isOdd(outsideTessFactor_f[edge]);
}
for (axis = 0; axis < QUAD_AXES; axis++) {
insideTessFactorOdd[axis] = isOdd(insideTessFactor_f[axis]) &&
(1.0f != insideTessFactor_f[axis]);
}
} else {
bool odd = (partitioning == LIBAGX_TESS_PARTITIONING_FRACTIONAL_ODD);
for (edge = 0; edge < QUAD_EDGES; edge++) {
outsideTessFactorOdd[edge] = odd;
}
insideTessFactorOdd[U] = insideTessFactorOdd[V] = odd;
}
// Save fixed point TessFactors
for (edge = 0; edge < QUAD_EDGES; edge++) {
outsideTessFactor[edge] = floatToFixed(outsideTessFactor_f[edge]);
}
for (axis = 0; axis < QUAD_AXES; axis++) {
insideTessFactor[axis] = floatToFixed(insideTessFactor_f[axis]);
}
if (partitioning != LIBAGX_TESS_PARTITIONING_FRACTIONAL_EVEN) {
// Special case if all TessFactors are 1
if ((FXP_ONE == insideTessFactor[U]) &&
(FXP_ONE == insideTessFactor[V]) &&
(FXP_ONE == outsideTessFactor[Ueq0]) &&
(FXP_ONE == outsideTessFactor[Veq0]) &&
(FXP_ONE == outsideTessFactor[Ueq1]) &&
(FXP_ONE == outsideTessFactor[Veq1])) {
/* Just do minimum tess factor */
if (output_primitive != LIBAGX_TESS_OUTPUT_POINT) {
ctx.Index = libagx_draw(p, mode, false, patch, 6);
if (mode == LIBAGX_TESS_MODE_COUNT)
return;
DefineClockwiseTriangle(&ctx, 0, 1, 3, /*indexStorageOffset*/ 0);
DefineClockwiseTriangle(&ctx, 1, 2, 3, /*indexStorageOffset*/ 3);
} else {
libagx_draw_points(&ctx, p, patch, 4);
}
global struct libagx_tess_point *points =
libagx_heap_alloc_points(p, patch, 4);
DefinePoint(&points[0], 0, 0);
DefinePoint(&points[1], FXP_ONE, 0);
DefinePoint(&points[2], FXP_ONE, FXP_ONE);
DefinePoint(&points[3], 0, FXP_ONE);
return;
}
}
// Compute TessFactor-specific metadata
for (int edge = 0; edge < QUAD_EDGES; edge++) {
ComputeTessFactorCtx(outsideTessFactorOdd[edge], outsideTessFactor[edge],
&outsideTessFactorCtx[edge]);
}
for (int axis = 0; axis < QUAD_AXES; axis++) {
ComputeTessFactorCtx(insideTessFactorOdd[axis], insideTessFactor[axis],
&insideTessFactorCtx[axis]);
}
int NumPoints = 0;
// outside edge offsets and storage
for (int edge = 0; edge < QUAD_EDGES; edge++) {
numPointsForOutsideEdge[edge] = NumPointsForTessFactor(
outsideTessFactorOdd[edge], outsideTessFactor[edge]);
NumPoints += numPointsForOutsideEdge[edge];
}
NumPoints -= 4;
// inside edge offsets
for (int axis = 0; axis < QUAD_AXES; axis++) {
numPointsForInsideTessFactor[axis] = NumPointsForTessFactor(
insideTessFactorOdd[axis], insideTessFactor[axis]);
int pointCountMin = insideTessFactorOdd[axis] ? 4 : 3;
// max() allows degenerate transition regions when inside TessFactor == 1
numPointsForInsideTessFactor[axis] =
max(pointCountMin, numPointsForInsideTessFactor[axis]);
}
insideEdgePointBaseOffset = NumPoints;
// inside storage, including interior edges above
int numInteriorPoints = (numPointsForInsideTessFactor[U] - 2) *
(numPointsForInsideTessFactor[V] - 2);
NumPoints += numInteriorPoints;
if (mode != LIBAGX_TESS_MODE_COUNT) {
ctx.Point = libagx_heap_alloc_points(p, patch, NumPoints);
// Generate exterior ring edge points, clockwise from top-left
int pointOffset = 0;
for (int edge = 0; edge < QUAD_EDGES; edge++) {
int odd = edge & 0x1;
// don't include end, since next edge starts with it.
int endPoint = numPointsForOutsideEdge[edge] - 1;
for (int p = 0; p < endPoint; p++, pointOffset++) {
FXP fxpParam;
int q =
((edge == 1) || (edge == 2)) ? p : endPoint - p; // reverse order
fxpParam = PlacePointIn1D(&outsideTessFactorCtx[edge],
outsideTessFactorOdd[edge], q);
if (odd) {
DefinePoint(&ctx.Point[pointOffset], fxpParam,
(edge == 3) ? FXP_ONE : 0);
} else {
DefinePoint(&ctx.Point[pointOffset], (edge == 2) ? FXP_ONE : 0,
fxpParam);
}
}
}
// Generate interior ring points, clockwise from (U==0,V==1) (bottom-left)
// spiralling toward center
int minNumPointsForTessFactor =
min(numPointsForInsideTessFactor[U], numPointsForInsideTessFactor[V]);
// note for even tess we aren't counting center point here.
int numRings = (minNumPointsForTessFactor >> 1);
for (int ring = 1; ring < numRings; ring++) {
int startPoint = ring;
int endPoint[QUAD_AXES] = {
numPointsForInsideTessFactor[U] - 1 - startPoint,
numPointsForInsideTessFactor[V] - 1 - startPoint,
};
for (int edge = 0; edge < QUAD_EDGES; edge++) {
int odd[QUAD_AXES] = {edge & 0x1, ((edge + 1) & 0x1)};
int perpendicularAxisPoint =
(edge < 2) ? startPoint : endPoint[odd[0]];
FXP fxpPerpParam = PlacePointIn1D(&insideTessFactorCtx[odd[0]],
insideTessFactorOdd[odd[0]],
perpendicularAxisPoint);
for (int p = startPoint; p < endPoint[odd[1]]; p++,
pointOffset++) // don't include end: next edge starts with
// it.
{
int q = ((edge == 1) || (edge == 2))
? p
: endPoint[odd[1]] - (p - startPoint);
FXP fxpParam = PlacePointIn1D(&insideTessFactorCtx[odd[1]],
insideTessFactorOdd[odd[1]], q);
if (odd[1]) {
DefinePoint(&ctx.Point[pointOffset], fxpPerpParam, fxpParam);
} else {
DefinePoint(&ctx.Point[pointOffset], fxpParam, fxpPerpParam);
}
}
}
}
// For even tessellation, the inner "ring" is degenerate - a row of points
if ((numPointsForInsideTessFactor[U] > numPointsForInsideTessFactor[V]) &&
!insideTessFactorOdd[V]) {
int startPoint = numRings;
int endPoint = numPointsForInsideTessFactor[U] - 1 - startPoint;
for (int p = startPoint; p <= endPoint; p++, pointOffset++) {
FXP fxpParam = PlacePointIn1D(&insideTessFactorCtx[U],
insideTessFactorOdd[U], p);
DefinePoint(&ctx.Point[pointOffset], fxpParam, FXP_ONE_HALF);
}
} else if ((numPointsForInsideTessFactor[V] >=
numPointsForInsideTessFactor[U]) &&
!insideTessFactorOdd[U]) {
int startPoint = numRings;
int endPoint = numPointsForInsideTessFactor[V] - 1 - startPoint;
for (int p = endPoint; p >= startPoint; p--, pointOffset++) {
FXP fxpParam = PlacePointIn1D(&insideTessFactorCtx[V],
insideTessFactorOdd[V], p);
DefinePoint(&ctx.Point[pointOffset], FXP_ONE_HALF, fxpParam);
}
}
}
if (output_primitive == LIBAGX_TESS_OUTPUT_POINT) {
libagx_draw_points(&ctx, p, patch, NumPoints);
return;
}
/* CONNECTIVITY */
{
// Generate primitives for all the concentric rings, one side at a time
// for each ring. +1 is so even tess includes the center point
int numPointRowsToCenter[QUAD_AXES] = {
(numPointsForInsideTessFactor[U] + 1) >> 1,
(numPointsForInsideTessFactor[V] + 1) >> 1,
};
int numRings = min(numPointRowsToCenter[U], numPointRowsToCenter[V]);
/* Calculate # of indices so we can allocate */
{
/* numPointsForInsideTessFactor >= 3 so numRings >= 2 */
assert(numRings >= 2);
/* Handle main case */
int OuterPoints =
numPointsForOutsideEdge[0] + numPointsForOutsideEdge[1] +
numPointsForOutsideEdge[2] + numPointsForOutsideEdge[3];
int InnerPoints =
numPointsForInsideTessFactor[U] + numPointsForInsideTessFactor[V];
int NumIndices = (OuterPoints * 3) + (12 * numRings * InnerPoints) -
(InnerPoints * 18) - (24 * numRings * (numRings - 1));
/* Determine major/minor axes */
bool U_major =
(numPointsForInsideTessFactor[U] > numPointsForInsideTessFactor[V]);
unsigned M = U_major ? U : V;
unsigned m = U_major ? V : U;
/* Handle degenerate ring */
if (insideTessFactorOdd[m]) {
assert(numPointsForInsideTessFactor[M] >=
numPointsForInsideTessFactor[m]);
NumIndices += 12 * ((numPointsForInsideTessFactor[M] >> 1) -
(numPointsForInsideTessFactor[m] >> 1));
NumIndices += (insideTessFactorOdd[M] ? 6 : 12);
}
// Generate the draw and allocate the index buffer with the size
ctx.Index = libagx_draw(p, mode, false, patch, NumIndices);
}
if (mode == LIBAGX_TESS_MODE_COUNT)
return;
int degeneratePointRing[QUAD_AXES] = {
// Even partitioning causes degenerate row of points,
// which results in exceptions to the point ordering conventions
// when travelling around the rings counterclockwise.
!insideTessFactorOdd[V] ? numPointRowsToCenter[V] - 1 : -1,
!insideTessFactorOdd[U] ? numPointRowsToCenter[U] - 1 : -1,
};
int numPointsForOutsideEdge_[QUAD_EDGES] = {
numPointsForOutsideEdge[Ueq0],
numPointsForOutsideEdge[Veq0],
numPointsForOutsideEdge[Ueq1],
numPointsForOutsideEdge[Veq1],
};
int insideEdgePointBaseOffset_ = insideEdgePointBaseOffset;
int outsideEdgePointBaseOffset = 0;
int NumIndices = 0;
for (int ring = 1; ring < numRings; ring++) {
int numPointsForInsideEdge[QUAD_AXES] = {
numPointsForInsideTessFactor[U] - 2 * ring,
numPointsForInsideTessFactor[V] - 2 * ring};
int edge0InsidePointBaseOffset = insideEdgePointBaseOffset_;
int edge0OutsidePointBaseOffset = outsideEdgePointBaseOffset;
for (int edge = 0; edge < QUAD_EDGES; edge++) {
int odd = (edge + 1) & 0x1;
int numTriangles =
numPointsForInsideEdge[odd] + numPointsForOutsideEdge_[edge] - 2;
int insideBaseOffset;
int outsideBaseOffset;
// We need to patch the indexing so Stitch() can think it sees 2
// sequentially increasing rows of points, even though we have
// wrapped around to the end of the inner and outer ring's points,
// so the last point is really the first point for the ring. We make
// it so that when Stitch() calls AddIndex(), that function will do
// any necessary index adjustment.
if (edge == 3) {
if (ring == degeneratePointRing[odd]) {
ctx.IndexPatchCtx2.baseIndexToInvert =
insideEdgePointBaseOffset_ + 1;
ctx.IndexPatchCtx2.cornerCaseBadValue =
outsideEdgePointBaseOffset +
numPointsForOutsideEdge_[edge] - 1;
ctx.IndexPatchCtx2.cornerCaseReplacementValue =
edge0OutsidePointBaseOffset;
ctx.IndexPatchCtx2.indexInversionEndPoint =
(ctx.IndexPatchCtx2.baseIndexToInvert << 1) - 1;
insideBaseOffset = ctx.IndexPatchCtx2.baseIndexToInvert;
outsideBaseOffset = outsideEdgePointBaseOffset;
ctx.bUsingPatchedIndices2 = true;
} else {
ctx.IndexPatchCtx.insidePointIndexDeltaToRealValue =
insideEdgePointBaseOffset_;
ctx.IndexPatchCtx.insidePointIndexBadValue =
numPointsForInsideEdge[odd] - 1;
ctx.IndexPatchCtx.insidePointIndexReplacementValue =
edge0InsidePointBaseOffset;
ctx.IndexPatchCtx.outsidePointIndexPatchBase =
ctx.IndexPatchCtx.insidePointIndexBadValue +
1; // past inside patched index range
ctx.IndexPatchCtx.outsidePointIndexDeltaToRealValue =
outsideEdgePointBaseOffset -
ctx.IndexPatchCtx.outsidePointIndexPatchBase;
ctx.IndexPatchCtx.outsidePointIndexBadValue =
ctx.IndexPatchCtx.outsidePointIndexPatchBase +
numPointsForOutsideEdge_[edge] - 1;
ctx.IndexPatchCtx.outsidePointIndexReplacementValue =
edge0OutsidePointBaseOffset;
insideBaseOffset = 0;
outsideBaseOffset =
ctx.IndexPatchCtx.outsidePointIndexPatchBase;
ctx.bUsingPatchedIndices = true;
}
} else if ((edge == 2) && (ring == degeneratePointRing[odd])) {
ctx.IndexPatchCtx2.baseIndexToInvert =
insideEdgePointBaseOffset_;
ctx.IndexPatchCtx2.cornerCaseBadValue = -1; // unused
ctx.IndexPatchCtx2.cornerCaseReplacementValue = -1; // unused
ctx.IndexPatchCtx2.indexInversionEndPoint =
ctx.IndexPatchCtx2.baseIndexToInvert << 1;
insideBaseOffset = ctx.IndexPatchCtx2.baseIndexToInvert;
outsideBaseOffset = outsideEdgePointBaseOffset;
ctx.bUsingPatchedIndices2 = true;
} else {
insideBaseOffset = insideEdgePointBaseOffset_;
outsideBaseOffset = outsideEdgePointBaseOffset;
}
if (ring == 1) {
StitchTransition(
&ctx, /*baseIndexOffset: */ NumIndices, insideBaseOffset,
insideTessFactorCtx[odd].numHalfTessFactorPoints,
insideTessFactorOdd[odd], outsideBaseOffset,
outsideTessFactorCtx[edge].numHalfTessFactorPoints,
outsideTessFactorOdd[edge]);
} else {
StitchRegular(&ctx, /*bTrapezoid*/ true, DIAGONALS_MIRRORED,
/*baseIndexOffset: */ NumIndices,
numPointsForInsideEdge[odd], insideBaseOffset,
outsideBaseOffset);
}
ctx.bUsingPatchedIndices = false;
ctx.bUsingPatchedIndices2 = false;
NumIndices += numTriangles * 3;
outsideEdgePointBaseOffset += numPointsForOutsideEdge_[edge] - 1;
if ((edge == 2) && (ring == degeneratePointRing[odd])) {
insideEdgePointBaseOffset_ -= numPointsForInsideEdge[odd] - 1;
} else {
insideEdgePointBaseOffset_ += numPointsForInsideEdge[odd] - 1;
}
numPointsForOutsideEdge_[edge] = numPointsForInsideEdge[odd];
}
}
// Triangulate center - a row of quads if odd
// This triangulation may be producing diagonals that are asymmetric about
// the center of the patch in this region.
if ((numPointsForInsideTessFactor[U] > numPointsForInsideTessFactor[V]) &&
insideTessFactorOdd[V]) {
ctx.bUsingPatchedIndices2 = true;
int stripNumQuads = (((numPointsForInsideTessFactor[U] >> 1) -
(numPointsForInsideTessFactor[V] >> 1))
<< 1) +
(insideTessFactorOdd[U] ? 1 : 2);
ctx.IndexPatchCtx2.baseIndexToInvert =
outsideEdgePointBaseOffset + stripNumQuads + 2;
ctx.IndexPatchCtx2.cornerCaseBadValue =
ctx.IndexPatchCtx2.baseIndexToInvert;
ctx.IndexPatchCtx2.cornerCaseReplacementValue =
outsideEdgePointBaseOffset;
ctx.IndexPatchCtx2.indexInversionEndPoint =
ctx.IndexPatchCtx2.baseIndexToInvert +
ctx.IndexPatchCtx2.baseIndexToInvert + stripNumQuads;
StitchRegular(
&ctx, /*bTrapezoid*/ false, DIAGONALS_INSIDE_TO_OUTSIDE,
/*baseIndexOffset: */ NumIndices,
/*numInsideEdgePoints:*/ stripNumQuads + 1,
/*insideEdgePointBaseOffset*/ ctx.IndexPatchCtx2.baseIndexToInvert,
outsideEdgePointBaseOffset + 1);
ctx.bUsingPatchedIndices2 = false;
NumIndices += stripNumQuads * 6;
} else if ((numPointsForInsideTessFactor[V] >=
numPointsForInsideTessFactor[U]) &&
insideTessFactorOdd[U]) {
ctx.bUsingPatchedIndices2 = true;
int stripNumQuads = (((numPointsForInsideTessFactor[V] >> 1) -
(numPointsForInsideTessFactor[U] >> 1))
<< 1) +
(insideTessFactorOdd[V] ? 1 : 2);
ctx.IndexPatchCtx2.baseIndexToInvert =
outsideEdgePointBaseOffset + stripNumQuads + 1;
ctx.IndexPatchCtx2.cornerCaseBadValue = -1; // unused
ctx.IndexPatchCtx2.indexInversionEndPoint =
ctx.IndexPatchCtx2.baseIndexToInvert +
ctx.IndexPatchCtx2.baseIndexToInvert + stripNumQuads;
DIAGONALS diag = insideTessFactorOdd[V]
? DIAGONALS_INSIDE_TO_OUTSIDE_EXCEPT_MIDDLE
: DIAGONALS_INSIDE_TO_OUTSIDE;
StitchRegular(
&ctx, /*bTrapezoid*/ false, diag,
/*baseIndexOffset: */ NumIndices,
/*numInsideEdgePoints:*/ stripNumQuads + 1,
/*insideEdgePointBaseOffset*/ ctx.IndexPatchCtx2.baseIndexToInvert,
outsideEdgePointBaseOffset);
ctx.bUsingPatchedIndices2 = false;
NumIndices += stripNumQuads * 6;
}
}
}