mirror of
https://github.com/hrydgard/ppsspp.git
synced 2026-10-17 16:30:30 +02:00
Each row of vertices is evaluated by folding the patch's four control point rows into four columns with the v weights, then weighting those along u, four vertices per register with one attribute component each. Cross products, normalization and the pole test become plain per-lane math, and the result is transposed into SimpleVertex order on the way out. The weights are stored one row per term so four steps load as one vector, and the control points are padded to full vectors in a single array. Colors now round to nearest on every platform. The SSE path already did, NEON and the scalar path truncated. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
269 lines
7.9 KiB
C++
269 lines
7.9 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/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;
|
|
};
|
|
|
|
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];
|
|
}
|
|
};
|