mirror of
https://github.com/hrydgard/ppsspp.git
synced 2026-10-14 23:10:33 +02:00
The same lerps as the GE-exact Bezier path, four at a time. At each v step, four control columns per vector share the v blending factors; then each row is evaluated at four u steps, each lane with its own six blending factors. A vector's u steps can straddle a segment boundary, so the row's points are gathered per lane, or splatted when all four are in one segment. The unit test compares it bit for bit with the scalar code, including tessellations that split a vector across segments. The GE-exact spline benchmark runs about twice as fast on an M1. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
277 lines
8.3 KiB
C++
277 lines
8.3 KiB
C++
// Copyright (c) 2013- PPSSPP Project.
|
|
|
|
// This program is free software: you can redistribute it and/or modify
|
|
// it under the terms of the GNU General Public License as published by
|
|
// the Free Software Foundation, version 2.0 or later versions.
|
|
|
|
// This program is distributed in the hope that it will be useful,
|
|
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
// GNU General Public License 2.0 for more details.
|
|
|
|
// A copy of the GPL 2.0 should have been included with the program.
|
|
// If not, see http://www.gnu.org/licenses/
|
|
|
|
// Official git repository and contact information can be found at
|
|
// https://github.com/hrydgard/ppsspp and http://www.ppsspp.org/.
|
|
|
|
#pragma once
|
|
#include <unordered_map>
|
|
|
|
#include "Common/CommonTypes.h"
|
|
#include "Common/Swap.h"
|
|
#include "Core/ConfigValues.h"
|
|
#include "GPU/Math3D.h"
|
|
#include "GPU/ge_constants.h"
|
|
#include "GPU/GPUState.h"
|
|
#include "GPU/Common/TransformCommon.h"
|
|
|
|
#include "Core/Config.h"
|
|
|
|
inline int half_ceil(int x) { return (x + 1) / 2; } // Integer ceil = (int)ceil((float)x / 2.0f)
|
|
|
|
class SimpleBufferManager;
|
|
|
|
namespace Spline {
|
|
|
|
void BuildIndex(u16 *indices, int &count, int num_u, int num_v, GEPatchPrimType prim_type, int total = 0);
|
|
|
|
class Bezier3DWeight;
|
|
class Spline3DWeight;
|
|
|
|
// We decode all vertices into a common format for easy interpolation and stuff.
|
|
// Not fast but can be optimized later.
|
|
|
|
struct SurfaceInfo {
|
|
int tess_u, tess_v;
|
|
int num_points_u, num_points_v;
|
|
int num_patches_u, num_patches_v;
|
|
int type_u, type_v;
|
|
GEPatchPrimType primType;
|
|
bool patchFacing;
|
|
|
|
void BaseInit() {
|
|
// If specified as 0, uses 1.
|
|
if (tess_u < 1) tess_u = 1;
|
|
if (tess_v < 1) tess_v = 1;
|
|
|
|
switch ((SplineQuality)g_Config.iSplineBezierQuality) {
|
|
case SplineQuality::LOW_QUALITY:
|
|
tess_u = 2;
|
|
tess_v = 2;
|
|
break;
|
|
case SplineQuality::MEDIUM_QUALITY:
|
|
// Don't cut below 2, though.
|
|
if (tess_u > 2) tess_u = half_ceil(tess_u);
|
|
if (tess_v > 2) tess_v = half_ceil(tess_v);
|
|
break;
|
|
default:
|
|
break;
|
|
}
|
|
}
|
|
|
|
// Only the larger factor, so a lopsided patch keeps at least one step along its short axis.
|
|
void ReduceLargerTess() {
|
|
if (tess_u >= tess_v) {
|
|
tess_u--;
|
|
} else {
|
|
tess_v--;
|
|
}
|
|
}
|
|
};
|
|
|
|
struct BezierSurface : public SurfaceInfo {
|
|
using WeightType = Bezier3DWeight;
|
|
|
|
int num_verts_per_patch;
|
|
// Evaluate as the GE does, bit exact but slower (the software renderer).
|
|
bool geExact = false;
|
|
|
|
void Init(int maxVertices) {
|
|
SurfaceInfo::BaseInit();
|
|
// Downsample until it fits, in case crazy tessellation factors are sent.
|
|
while ((tess_u + 1) * (tess_v + 1) * num_patches_u * num_patches_v > maxVertices && (tess_u > 1 || tess_v > 1)) {
|
|
ReduceLargerTess();
|
|
}
|
|
num_verts_per_patch = (tess_u + 1) * (tess_v + 1);
|
|
}
|
|
|
|
int GetTessStart(int patch) const { return 0; }
|
|
|
|
int GetPointIndex(int patch_u, int patch_v) const { return patch_v * 3 * num_points_u + patch_u * 3; }
|
|
|
|
int GetIndexU(int patch_u, int tile_u) const { return tile_u; }
|
|
int GetIndexV(int patch_v, int tile_v) const { return tile_v; }
|
|
|
|
int GetIndex(int index_u, int index_v, int patch_u, int patch_v) const {
|
|
int patch_index = patch_v * num_patches_u + patch_u;
|
|
return index_v * (tess_u + 1) + index_u + num_verts_per_patch * patch_index;
|
|
}
|
|
|
|
void BuildIndex(u16 *indices, int &count) const {
|
|
for (int patch_u = 0; patch_u < num_patches_u; ++patch_u) {
|
|
for (int patch_v = 0; patch_v < num_patches_v; ++patch_v) {
|
|
int patch_index = patch_v * num_patches_u + patch_u;
|
|
int total = patch_index * num_verts_per_patch;
|
|
Spline::BuildIndex(indices + count, count, tess_u, tess_v, primType, total);
|
|
}
|
|
}
|
|
}
|
|
};
|
|
|
|
struct SplineSurface : public SurfaceInfo {
|
|
using WeightType = Spline3DWeight;
|
|
|
|
int num_vertices_u;
|
|
// Evaluate as the GE does, bit exact but slower (the software renderer).
|
|
bool geExact = false;
|
|
|
|
void Init(int maxVertices) {
|
|
SurfaceInfo::BaseInit();
|
|
// Downsample until it fits, in case crazy tessellation factors are sent.
|
|
while ((num_patches_u * tess_u + 1) * (num_patches_v * tess_v + 1) > maxVertices && (tess_u > 1 || tess_v > 1)) {
|
|
ReduceLargerTess();
|
|
}
|
|
num_vertices_u = num_patches_u * tess_u + 1;
|
|
}
|
|
|
|
int GetTessStart(int patch) const { return (patch == 0) ? 0 : 1; }
|
|
|
|
int GetPointIndex(int patch_u, int patch_v) const { return patch_v * num_points_u + patch_u; }
|
|
|
|
int GetIndexU(int patch_u, int tile_u) const { return patch_u * tess_u + tile_u; }
|
|
int GetIndexV(int patch_v, int tile_v) const { return patch_v * tess_v + tile_v; }
|
|
|
|
int GetIndex(int index_u, int index_v, int patch_u, int patch_v) const {
|
|
return index_v * num_vertices_u + index_u;
|
|
}
|
|
|
|
void BuildIndex(u16 *indices, int &count) const {
|
|
Spline::BuildIndex(indices, count, num_patches_u * tess_u, num_patches_v * tess_v, primType);
|
|
}
|
|
};
|
|
|
|
// The four basis weights and their derivatives at each tessellation step along one axis, one row
|
|
// per term, so that four neighbouring steps load as one vector. Each row has WEIGHT_PADDING zeros
|
|
// past the end, for a vector that starts at one of the last steps.
|
|
struct WeightTable {
|
|
enum { WEIGHT_PADDING = 3 };
|
|
|
|
float *data = nullptr;
|
|
int stride = 0;
|
|
|
|
const float *Basis(int k) const { return data + k * stride; }
|
|
const float *Deriv(int k) const { return data + (4 + k) * stride; }
|
|
};
|
|
|
|
template<class T>
|
|
class WeightCache : public T {
|
|
private:
|
|
std::unordered_map<u32, WeightTable> weightsCache;
|
|
public:
|
|
const WeightTable &operator [] (u32 key) {
|
|
WeightTable &weights = weightsCache[key];
|
|
if (!weights.data)
|
|
weights = T::CalcWeightsAll(key);
|
|
return weights;
|
|
}
|
|
|
|
void Clear() {
|
|
for (auto &it : weightsCache)
|
|
delete[] it.second.data;
|
|
weightsCache.clear();
|
|
}
|
|
};
|
|
|
|
struct Weight2D {
|
|
const WeightTable *u, *v;
|
|
|
|
template<class T>
|
|
Weight2D(WeightCache<T> &cache, u32 key_u, u32 key_v) {
|
|
u = &cache[key_u];
|
|
v = (key_u != key_v) ? &cache[key_v] : u; // Use same weights if u == v
|
|
}
|
|
};
|
|
|
|
// Everything the tessellator reads from one control point, each attribute padded to a full vector.
|
|
struct alignas(16) ControlPoint {
|
|
float pos[4]; // w is 0.
|
|
float col[4]; // 0-255.
|
|
float uv[4]; // zw are 0.
|
|
};
|
|
|
|
struct ControlPoints {
|
|
ControlPoint *points = nullptr;
|
|
u32_le defcolor;
|
|
|
|
ControlPoints() {}
|
|
ControlPoints(const SimpleVertex *const *points, int size, SimpleBufferManager &managedBuf);
|
|
void Convert(const SimpleVertex *const *points, int size);
|
|
bool IsValid() const {
|
|
return points != nullptr;
|
|
}
|
|
};
|
|
|
|
struct OutputBuffers {
|
|
SimpleVertex *vertices;
|
|
u16 *indices;
|
|
int count;
|
|
// Applied to the UVs, so the vertices come out the way the vertex decoder would decode them.
|
|
UVScale uvScale{ 1.0f, 1.0f, 0.0f, 0.0f };
|
|
// Out: whether every vertex has alpha 255, which the vertex decoder tracks in gstate_c.vertexFullAlpha.
|
|
bool fullAlpha = true;
|
|
};
|
|
|
|
// Set to evaluate the GE's Bezier patches and splines with the scalar code, which the vectorized code is tested against.
|
|
extern bool g_splineGEScalar;
|
|
|
|
template<class Surface>
|
|
void SoftwareTessellation(OutputBuffers &output, const Surface &surface, u32 origVertType, const ControlPoints &points);
|
|
|
|
} // namespace Spline
|
|
|
|
// Define function object for TemplateParameterDispatcher
|
|
#define TEMPLATE_PARAMETER_DISPATCHER_FUNCTION(NAME, FUNCNAME, FUNCTYPE) \
|
|
struct NAME { \
|
|
template<bool ...Params> \
|
|
static FUNCTYPE GetFunc() { \
|
|
return &FUNCNAME<Params...>; \
|
|
} \
|
|
};
|
|
|
|
template<typename Func, int NumParams, class Dispatcher>
|
|
class TemplateParameterDispatcher {
|
|
|
|
// Store all combinations of template functions into an array
|
|
template<int LoopCount, int Index = 0, bool ...Params>
|
|
struct Initializer {
|
|
static void Init(Func funcs[]) {
|
|
Initializer<LoopCount - 1, (Index << 1) + 1, true, Params...>::Init(funcs); // true
|
|
Initializer<LoopCount - 1, (Index << 1) + 0, false, Params...>::Init(funcs); // false
|
|
}
|
|
};
|
|
// Specialized for terminates the recursive loop
|
|
template<int Index, bool ...Params>
|
|
struct Initializer<0, Index, Params...> {
|
|
static void Init(Func funcs[]) {
|
|
funcs[Index] = Dispatcher::template GetFunc<Params...>(); // Resolve the nested dependent name as template function.
|
|
}
|
|
};
|
|
|
|
private:
|
|
Func funcs[1 << NumParams]; /* Function pointers array */
|
|
public:
|
|
TemplateParameterDispatcher() {
|
|
Initializer<NumParams>::Init(funcs);
|
|
}
|
|
|
|
Func GetFunc(const bool params[]) const {
|
|
// Convert bool parameters to index of the array
|
|
int index = 0;
|
|
for (int i = 0; i < NumParams; ++i) {
|
|
index |= params[i] << i;
|
|
}
|
|
return funcs[index];
|
|
}
|
|
};
|