Nugget
Bare-metal libraries and examples for the original PlayStation
Loading...
Searching...
No Matches
gte-math.hh
Go to the documentation of this file.
1/*
2
3MIT License
4
5Copyright (c) 2026 PCSX-Redux authors
6
7Permission is hereby granted, free of charge, to any person obtaining a copy
8of this software and associated documentation files (the "Software"), to deal
9in the Software without restriction, including without limitation the rights
10to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
11copies of the Software, and to permit persons to whom the Software is
12furnished to do so, subject to the following conditions:
13
14The above copyright notice and this permission notice shall be included in all
15copies or substantial portions of the Software.
16
17THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
18IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
19FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
20AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
21LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
22OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
23SOFTWARE.
24
25*/
26
27#pragma once
28
29#include "psyqo/bezier.hh"
30#include "psyqo/fixed-point.hh"
31#include "psyqo/gte-kernels.hh"
33#include "psyqo/matrix.hh"
34#include "psyqo/vector.hh"
35
36/*
37 * GteMath - the SoftMath operations that the GTE can do in hardware.
38 *
39 * These mirror SoftMath's signatures and argument conventions exactly, so they
40 * are drop-in. Where SoftMath has a quirk, this keeps the quirk rather than
41 * fixing it, because a caller swapping one for the other should not have to
42 * re-derive anything. multiplyMatrix33 in particular computes out = m2 * m1,
43 * matching SoftMath.
44 *
45 * WHY THESE ARE FREE FUNCTIONS AND NOT A SCOPED, REGISTER-OWNING OBJECT.
46 * Measured on hardware with a GTE-versus-SoftMath throughput benchmark,
47 * cycles per element,
48 * SCPH-5501, caller at -O2, "cold" meaning the matrix upload is paid INSIDE the
49 * call, once per element:
50 *
51 * operation soft cold hot cold speedup
52 * matrixVecMul3 271.66 126.58 47.63 2.15x
53 * matrixVecMul3xy 170.70 123.60 45.71 1.38x
54 * crossProductVec3 187.76 73.32 46.56 2.56x
55 * project 709.41 140.03 52.58 5.07x
56 * multiplyMatrix33 1144.06 209.38 127.63 5.46x
57 *
58 * There is no crossover: the GTE wins at a batch of one for everything
59 * measured, so the simple form never loses and needs no caveats. Hoisting the
60 * setup is still worth about 2.7x on top, which is what the batching helpers
61 * further down are for - but that is an optimisation, not the architecture.
62 *
63 * The reason the software side is so expensive is not instruction count. On the
64 * R3000A `mult` and `divu` are multi-cycle and `mflo` interlocks, so every
65 * FixedPoint multiply stalls. Three independent operations above work out at
66 * 28-31 cycles per fixed-point multiply. `project` is dominated by a single
67 * `divu`: 681 of its 709 cycles are the one divide.
68 *
69 * INLINING. psyqo builds at -Os, where GCC does NOT inline the GTE register
70 * accessors - it emits `jal` into writeSafe/read, and that measured up to +67%
71 * on the cold numbers above. Everything here is therefore in the header and
72 * marked always_inline. Do not move these to a .cpp built at -Os.
73 *
74 * REGISTER CLOBBERS. Each function documents what it destroys. This is the real
75 * API surface: a caller who has a rotation matrix loaded for projection cannot
76 * casually call crossProductVec3 in the middle of a render loop, because the
77 * GTE's cross product uses the rotation matrix DIAGONAL as one of its operands.
78 * That one is a landmine, so it is spelled out on every function rather than
79 * once at the top.
80 *
81 * RESTORING. Prefer re-uploading from the copy you still have in RAM over
82 * reading registers back. A Matrix33 is five ctc2; reading it back is nine
83 * mfc2 plus hazard nops. "Save and restore" is the expensive direction here.
84 */
85
86namespace psyqo {
87
88namespace GteMath {
89
90#define PSYQO_GTE_MATH_INLINE [[gnu::always_inline]] static inline
91
101 GTE::writeUnsafe<GTE::PseudoRegister::Rotation>(m);
102 GTE::writeSafe<GTE::PseudoRegister::V0>(v);
103 GTE::Kernels::mvmva<GTE::Kernels::MX::RT, GTE::Kernels::MV::V0>();
104 *out = Vec3(GTE::readSafe<GTE::PseudoRegister::SV>());
105}
106
113 GTE::writeUnsafe<GTE::PseudoRegister::Rotation>(m);
114 GTE::writeSafe<GTE::PseudoRegister::V0>(v);
115 GTE::Kernels::mvmva<GTE::Kernels::MX::RT, GTE::Kernels::MV::V0>();
116 Vec3 r = Vec3(GTE::readSafe<GTE::PseudoRegister::SV>());
117 out->x = r.x;
118 out->y = r.y;
119}
120
126PSYQO_GTE_MATH_INLINE FixedPoint<> matrixVecMul3z(const Matrix33 &m, const Vec3 &v) {
127 GTE::writeUnsafe<GTE::PseudoRegister::Rotation>(m);
128 GTE::writeSafe<GTE::PseudoRegister::V0>(v);
129 GTE::Kernels::mvmva<GTE::Kernels::MX::RT, GTE::Kernels::MV::V0>();
130 return Vec3(GTE::readSafe<GTE::PseudoRegister::SV>()).z;
131}
132
143 return {{
144 {c0.x, c1.x, c2.x},
145 {c0.y, c1.y, c2.y},
146 {c0.z, c1.z, c2.z},
147 }};
148}
149
163 GTE::writeUnsafe<GTE::PseudoRegister::Rotation>(m2);
164 const Vec3 cols[3] = {
165 {m1.vs[0].x, m1.vs[1].x, m1.vs[2].x},
166 {m1.vs[0].y, m1.vs[1].y, m1.vs[2].y},
167 {m1.vs[0].z, m1.vs[1].z, m1.vs[2].z},
168 };
169 Vec3 res[3];
170 for (unsigned j = 0; j < 3; j++) {
171 GTE::writeSafe<GTE::PseudoRegister::V0>(cols[j]);
172 GTE::Kernels::mvmva<GTE::Kernels::MX::RT, GTE::Kernels::MV::V0>();
173 res[j] = Vec3(GTE::readSafe<GTE::PseudoRegister::SV>());
174 }
175 out->vs[0] = {res[0].x, res[1].x, res[2].x};
176 out->vs[1] = {res[0].y, res[1].y, res[2].y};
177 out->vs[2] = {res[0].z, res[1].z, res[2].z};
178}
179
193 Vec3 a = v1;
194 Vec3 b = v2;
195 GTE::write<GTE::Register::R11R12, GTE::Unsafe>(a.x.raw());
196 GTE::write<GTE::Register::R22R23, GTE::Unsafe>(a.y.raw());
197 GTE::write<GTE::Register::R33, GTE::Unsafe>(a.z.raw());
198 GTE::write<GTE::Register::IR1, GTE::Unsafe>(reinterpret_cast<uint32_t *>(&b.x));
199 GTE::write<GTE::Register::IR2, GTE::Unsafe>(reinterpret_cast<uint32_t *>(&b.y));
200 GTE::write<GTE::Register::IR3, GTE::Safe>(reinterpret_cast<uint32_t *>(&b.z));
201 GTE::Kernels::cp();
202 GTE::read<GTE::PseudoRegister::LV>(*out);
203}
204
223PSYQO_GTE_MATH_INLINE FixedPoint<> inverseSquareRootSeed(FixedPoint<> x) {
224 GTE::write<GTE::Register::LZCS, GTE::Safe>(x.raw());
225 int32_t shift = (5 + int32_t(GTE::readRaw<GTE::Register::LZCR>())) / 2;
226 return FixedPoint<>(int32_t(1) << shift, FixedPoint<>::RAW);
227}
228
245void normalizeVec3(Vec3 *v);
246
258
278PSYQO_GTE_MATH_INLINE Vec3 cubic(const Vec3 &a, const Vec3 &b, const Vec3 &c, const Vec3 &d, FixedPoint<> t) {
279 using namespace psyqo::fixed_point_literals;
280 FixedPoint<> mt = 1.0_fp - t;
281 FixedPoint<> mt2 = mt * mt;
282 FixedPoint<> t2 = t * t;
283 FixedPoint<> f4 = t2 * t;
284 GTE::writeUnsafe<GTE::PseudoRegister::Rotation>(fromColumns(a, b, c));
285 GTE::writeUnsafe<GTE::PseudoRegister::Translation>(Vec3{d.x * f4, d.y * f4, d.z * f4});
286 GTE::writeSafe<GTE::PseudoRegister::V0>(Vec3{mt2 * mt, mt2 * t * 3, mt * t2 * 3});
287 GTE::Kernels::mvmva<GTE::Kernels::MX::RT, GTE::Kernels::MV::V0, GTE::Kernels::TV::TR>();
288 return Vec3(GTE::readSafe<GTE::PseudoRegister::SV>());
289}
290
304PSYQO_GTE_MATH_INLINE Vec3 cubicDerivative(const Vec3 &a, const Vec3 &b, const Vec3 &c, const Vec3 &d, FixedPoint<> t) {
305 using namespace psyqo::fixed_point_literals;
306 FixedPoint<> mt = 1.0_fp - t;
307 Vec3 ab = {b.x - a.x, b.y - a.y, b.z - a.z};
308 Vec3 bc = {c.x - b.x, c.y - b.y, c.z - b.z};
309 Vec3 cd = {d.x - c.x, d.y - c.y, d.z - c.z};
310 GTE::writeUnsafe<GTE::PseudoRegister::Rotation>(fromColumns(ab, bc, cd));
311 GTE::writeSafe<GTE::PseudoRegister::V0>(Vec3{mt * mt, mt * t * 2, t * t});
312 GTE::Kernels::mvmva<GTE::Kernels::MX::RT, GTE::Kernels::MV::V0>();
313 return Vec3(GTE::readSafe<GTE::PseudoRegister::SV>());
314}
315
316#undef PSYQO_GTE_MATH_INLINE
317
318} // namespace GteMath
319
320} // namespace psyqo
uint32_t t
Definition cdltest.c:291
uint32_t r
Definition cpu.c:310
uint32_t out
Definition cpu.c:62
uint8_t cd
Definition gte-depthcue.c:208
uint8_t b
Definition gte-depthcue.c:41
#define PSYQO_GTE_MATH_INLINE
Definition gte-math.hh:90
volatile void * c1
Definition load-timings.c:874
volatile void * c0
Definition load-timings.c:873
PSYQO_GTE_MATH_INLINE void fastNormalizeVec3(Vec3 *v)
Normalises a vector in place, without the exact square root.
Definition gte-math.hh:257
void normalizeVec3(Vec3 *v)
Normalises a vector in place.
Definition gte-math.cpp:38
PSYQO_GTE_MATH_INLINE Vec3 cubic(const Vec3 &a, const Vec3 &b, const Vec3 &c, const Vec3 &d, FixedPoint<> t)
Evaluates a cubic Bezier at t.
Definition gte-math.hh:278
PSYQO_GTE_MATH_INLINE void multiplyMatrix33(const Matrix33 &m1, const Matrix33 &m2, Matrix33 *out)
Multiplies two matrices.
Definition gte-math.hh:162
PSYQO_GTE_MATH_INLINE FixedPoint matrixVecMul3z(const Matrix33 &m, const Vec3 &v)
Multiplies a vector by a matrix, keeping only z.
Definition gte-math.hh:126
PSYQO_GTE_MATH_INLINE Matrix33 fromColumns(const Vec3 &c0, const Vec3 &c1, const Vec3 &c2)
Builds a matrix from three column vectors.
Definition gte-math.hh:142
PSYQO_GTE_MATH_INLINE void matrixVecMul3xy(const Matrix33 &m, const Vec3 &v, Vec2 *out)
Multiplies a vector by a matrix, keeping only x and y.
Definition gte-math.hh:112
PSYQO_GTE_MATH_INLINE Vec3 cubicDerivative(const Vec3 &a, const Vec3 &b, const Vec3 &c, const Vec3 &d, FixedPoint<> t)
Evaluates the derivative of a cubic Bezier at t.
Definition gte-math.hh:304
PSYQO_GTE_MATH_INLINE void matrixVecMul3(const Matrix33 &m, const Vec3 &v, Vec3 *out)
Multiplies a vector by a matrix.
Definition gte-math.hh:100
PSYQO_GTE_MATH_INLINE FixedPoint inverseSquareRootSeed(FixedPoint<> x)
The seed for an inverse square root, from the GTE's leading-bit count.
Definition gte-math.hh:223
PSYQO_GTE_MATH_INLINE void crossProductVec3(const Vec3 &v1, const Vec3 &v2, Vec3 *out)
Cross product of two vectors.
Definition gte-math.hh:192
Definition lua.hh:40
Vector< 3 > Vec3
Definition vector.hh:242
Definition matrix.hh:33
Vec3 vs[3]
Definition matrix.hh:34
FixedPoint< precisionBits, T > x
Definition vector.hh:49
PSYQO_NO_UNIQUE_ADDR std::conditional_t<(N > 2), FixedPoint< precisionBits, T >, EmptyZ > z
Definition vector.hh:51
FixedPoint< precisionBits, T > y
Definition vector.hh:49
static int c
Definition syscalls.h:172
uint16_t v2
Definition timers.c:262
uint16_t v1
Definition timers.c:260