OverviewFilesCommitsIssuesDocsReleasesPeers

proc-nodes commits / 9717c7f

perlin: the third .inc, and a gate that stops taking the machine down with it
blender_perlin.h joins the hash and the erosion filter: 473 lines of arithmetic become 180 lines of conversion over src/shared/blender_perlin.inc, compiled as GLSL and as C++ from one text. Callers did not change -- including the explicit template spellings perlin_fractal_distorted<Vector3>(...) and perlin_fbm<Vector3>(...), which are now thin specialisations picking a per-dimension suffix. .inc vs the old header 752,000/752,000 bit-exact, both coordinate regimes CPU vs GPU 2496/2496 (1056 exact, 1440 near) blender 15/15 noise texture, 11/11 gradient noise terrain path correlation 1.000000, RMS 1.967e-07 -- unmoved golden / op parity 12/12, 708/708 THREE WAYS OUT OF THE SUBSET, all shape and not arithmetic: GLSL HAS NO TEMPLATES. Five fractal types x four dimensions is twenty bodies, and hand-copying them is the transcription this exercise exists to avoid -- one wrong suffix and a dimension silently evaluates another. The body is written ONCE and the preprocessor stamps it out with ##, which glslang supports and which both the gate and Godot's RenderingDevice use. The functions that are OVERLOADS in Blender's C++ stay overloads, so the structure still matches the source. THE REFERENCE OUT-PARAMETER. floor_fraction wrote its integer through `int &`; GLSL's `inout` is not the same thing, so it is two functions over one floor(). fmodf IS TRUNCATED, GLSL's mod() IS FLOORED. bp_fmod spells out the truncated form. Below 100000 the rewrite is exactly fmodf; above it the division rounds where fmodf is exact, so the equivalence run classifies the wrap separately -- and measured 0 differing there over ~188,000 samples. ⚠ EVERY DECIMAL LITERAL CARRIES AN `f`, because `0.9820` is a float in GLSL and a DOUBLE in C++. GLSL accepts the suffix (measured). The four constants that are not exactly representable happen to round the same either way, also measured -- but that is a coin the next constant need not land on. --- THE GATE HAD TO BE REBUILT, AND THE REASON IS THE INTERESTING PART. test_hash_parity became test_shared_noise_parity, and stopped emitting one line of GLSL per case. The old shape came from test_emitted_op_parity's reasoning -- "246 pipelines to answer 246 questions is a slow gate nobody runs" -- which is sound for 658 cheap Math cases and does not survive 1300 perlin cases, each an eight-octave chain. I scaled the case count 3.6x and the per-case body by orders of magnitude without revisiting the assumption that made the shape sound. It twice took this 31 GB machine to global OOM -- 13.9 GB then 16.1 GB resident -- and killed the session around it. Not a GPU fault: no Xid, no NVRM, nothing in the kernel log but the OOM killer, the second time invoked by the editor process itself. Worse than slow, though: with the inputs baked in as literals glslc tried to CONSTANT-FOLD every chain. A folded case is the compiler's arithmetic and not the GPU's, so the gate would have quietly stopped involving the machine it exists to test. Now: one record per case in a buffer, one dispatcher in the shader, cases across invocations. Shader size is O(node families), not O(cases) -- 40626 bytes whether there are ten cases or ten thousand -- and every numeric input is opaque to the optimiser, so the GPU actually runs. Peak RSS 149 MiB against 16 GB. The opcode constants are EMITTED INTO THE SHADER from the C++ enum so the two dispatchers cannot drift by editing one. And it caps its own address space at 4 GiB. Starved to 200 MB it dies at vkEnumeratePhysicalDevices with VK_ERROR_INITIALIZATION_FAILED, which names nothing useful on its own -- so die() reports the cap on every failure, since a reader who does not know a cap is in force will go and blame the driver. Mutation-checked after the rewrite: 0.9820f -> 0.9821f in the 3D scale constant fails 241 cases and EVERY ONE of them is dim=3. The 1D, 2D and 4D families and all 1056 exact hash cases survive, which is what a per-dimension constant should do. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
9717c7f4dc1a4946bfbe434998a30d9f72e87307
by plankatron <fleeky@gmail.com> · 2026-09-06T00:08:34.000Z

Changed files (6) +1650 -1006

SConstruct +8 -0
……@@ -58,6 +58,14 @@ glsl_inc_gen = [
5858 "src/shared/blender_hash.inc",
5959 "python3 tools/gen_glsl_inc.py $SOURCE $TARGET BLENDER_HASH_GLSL",
6060 ),
61 # Perlin, third .inc. Depends on the hash and does not include it — GLSL
62 # has no #include, so the emitter concatenates hash-then-perlin and the
63 # C++ shim includes them in that order.
64 env.Command(
65 "src/gpu/blender_perlin_glsl.gen.h",
66 "src/shared/blender_perlin.inc",
67 "python3 tools/gen_glsl_inc.py $SOURCE $TARGET BLENDER_PERLIN_GLSL",
68 ),
6169]
6270
6371sources = (
src/shared/blender_noise_cpp_shim.h +50 -1
……@@ -27,6 +27,7 @@
2727// site, which is what blender_noise.h now is.
2828// =========================================================================
2929
30#include <cmath>
3031#include <cstdint>
3132#include <cstring>
3233
……@@ -99,8 +100,56 @@ inline uint floatBitsToUint(float f) {
99100 return i;
100101}
101102
102// The algorithm itself. Same text the shader compiles.
103// =========================================================================
104// ARITHMETIC, for blender_perlin.inc.
105//
106// The hash layer needed NONE of this -- it only ever built a vector and read
107// its components, and an operator nobody calls is somewhere for GLSL and C++
108// to differ unnoticed. Perlin genuinely does vector arithmetic (p * fscale,
109// p + offset), so the operators exist now, and every one of them is
110// COMPONENTWISE because that is what GLSL means by vec * vec. A dot product
111// here would be silent and catastrophic.
112// =========================================================================
113
114inline vec2 operator+(vec2 a, vec2 b) { return vec2(a.x + b.x, a.y + b.y); }
115inline vec2 operator-(vec2 a, vec2 b) { return vec2(a.x - b.x, a.y - b.y); }
116inline vec2 operator*(vec2 a, vec2 b) { return vec2(a.x * b.x, a.y * b.y); }
117inline vec2 operator*(vec2 a, float s) { return vec2(a.x * s, a.y * s); }
118inline vec2 operator*(float s, vec2 a) { return vec2(s * a.x, s * a.y); }
119inline vec2 operator+(vec2 a, float s) { return vec2(a.x + s, a.y + s); }
120inline vec2 operator-(vec2 a) { return vec2(-a.x, -a.y); }
121
122inline vec3 operator+(vec3 a, vec3 b) { return vec3(a.x + b.x, a.y + b.y, a.z + b.z); }
123inline vec3 operator-(vec3 a, vec3 b) { return vec3(a.x - b.x, a.y - b.y, a.z - b.z); }
124inline vec3 operator*(vec3 a, vec3 b) { return vec3(a.x * b.x, a.y * b.y, a.z * b.z); }
125inline vec3 operator*(vec3 a, float s) { return vec3(a.x * s, a.y * s, a.z * s); }
126inline vec3 operator*(float s, vec3 a) { return vec3(s * a.x, s * a.y, s * a.z); }
127inline vec3 operator+(vec3 a, float s) { return vec3(a.x + s, a.y + s, a.z + s); }
128inline vec3 operator-(vec3 a) { return vec3(-a.x, -a.y, -a.z); }
129
130inline vec4 operator+(vec4 a, vec4 b) { return vec4(a.x + b.x, a.y + b.y, a.z + b.z, a.w + b.w); }
131inline vec4 operator-(vec4 a, vec4 b) { return vec4(a.x - b.x, a.y - b.y, a.z - b.z, a.w - b.w); }
132inline vec4 operator*(vec4 a, vec4 b) { return vec4(a.x * b.x, a.y * b.y, a.z * b.z, a.w * b.w); }
133inline vec4 operator*(vec4 a, float s) { return vec4(a.x * s, a.y * s, a.z * s, a.w * s); }
134inline vec4 operator*(float s, vec4 a) { return vec4(s * a.x, s * a.y, s * a.z, s * a.w); }
135inline vec4 operator+(vec4 a, float s) { return vec4(a.x + s, a.y + s, a.z + s, a.w + s); }
136inline vec4 operator-(vec4 a) { return vec4(-a.x, -a.y, -a.z, -a.w); }
137
138// -- Scalar builtins. float-only, because the .inc writes GLSL literals which
139// are DOUBLE in C++, and letting one of these promote would compute a
140// perlin octave in double on the CPU and in float on the GPU.
141inline float floor(float x) { return std::floor(x); }
142inline float trunc(float x) { return std::trunc(x); }
143inline float abs(float x) { return std::fabs(x); }
144inline float min(float a, float b) { return a < b ? a : b; }
145inline float max(float a, float b) { return a > b ? a : b; }
146inline float clamp(float v, float lo, float hi) { return v < lo ? lo : (v > hi ? hi : v); }
147
148// The algorithms themselves. Same text the shader compiles, and in the same
149// order the emitter concatenates them: perlin's lattice gradients ARE the
150// hash, and GLSL has no #include to say so.
103151#include "blender_hash.inc"
152#include "blender_perlin.inc"
104153
105154} // namespace bnoisegl
106155} // namespace godot
src/shared/blender_perlin.h +124 -421
……@@ -1,32 +1,42 @@
11#ifndef PROC_NODES_BLENDER_PERLIN_H
22#define PROC_NODES_BLENDER_PERLIN_H
33
4// BLENDER'S PERLIN NOISE, PORTED FROM BLENDER'S SOURCE.
4// BLENDER'S PERLIN NOISE — NOW AN ADAPTER, NOT AN IMPLEMENTATION.
55//
6// From the "Perlin Noise" section of
7// `source/blender/blenlib/intern/noise.cc`, Blender source commit 61a36e08,
8// BLENDER_VERSION 502. Blender's own header notes it is functionally identical
9// to the EEVEE, OSL and SVM implementations, so any change belongs in all of
10// them; here it needs to stay identical to Blender's, which is the point.
6// The arithmetic that used to live here is in src/shared/blender_perlin.inc,
7// compiled BOTH as GLSL and as C++, the same arrangement erosion_filter.inc
8// and blender_hash.inc have. This header is the godot-typed face of it, and
9// every name and signature callers used is still here -- including the
10// explicit template spellings `perlin_fractal_distorted<Vector3>(...)` and
11// `perlin_fbm<Vector3>(...)`, which noise_nodes.cpp writes -- so nothing
12// downstream changed.
1113//
12// The 3D FBM Factor was ALREADY exact before this -- 1.110e-16 against
13// Blender -- because the erosion work checked that one path. Everything else
14// was not: the other four fractal types, 1D/2D/4D, `normalize`, `distortion`,
15// and the Color output, which ours returned as the factor repeated in grey
16// where Blender's is a THREE-COMPONENT noise at different seeds.
14// WHY IT MOVED. There were two Perlins: this port, and a hand-written GLSL
15// copy in GpuCompiler::get_noise_library_glsl() carrying its own Jenkins hash,
16// its own fade, its own gradients and the same unexplained 0.9820. Nothing
17// compared them. The Noise Texture node could not be given GPU support without
18// choosing which of the two to believe.
1719//
18// Two things in here could not have been guessed:
20// GLSL HAS NO TEMPLATES, so the five fractal types -- which are
21// `template <typename T>` in Blender's C++ -- are expanded per dimension by
22// the preprocessor in the .inc, and the templates here are thin
23// specialisations that pick the matching suffix. The dimension is chosen ONCE,
24// by C++ overload resolution on the caller's type, instead of by a hand-copied
25// suffix in twenty places.
26//
27// Two things in the .inc could not have been guessed and are worth repeating
28// where people look:
1929//
2030// the per-dimension SCALE CONSTANTS on perlin_signed -- 0.2500, 0.6616,
21// 0.9820, 0.8344 -- which the OSL developers found experimentally to remap
22// each dimension's output to [-1, 1]. 0.9820 is the 3D one, and it is the
23// same 0.982 that appeared as an unexplained "normalization factor matching
31// 0.9820, 0.8344 -- found experimentally by the OSL developers to remap each
32// dimension's output to [-1, 1]. 0.9820 is the 3D one, and it is the same
33// 0.982 that appeared as an unexplained "normalization factor matching
2434// iq/Blender" in ProcNodes_Noised, where it did not belong.
2535//
26// the SEEDS of the Color output's second and third components, which differ
27// per dimension: 1D uses offsets 1 and 2, 2D uses 2 and 3, 3D uses 3 and 4,
28// 4D uses 4 and 5. Blender calls them "arbitrary seeds ... for compatibility
29// with shading functions", and there is no rule to derive them from.
36// the SEEDS of the Colour output's second and third components, which differ
37// per dimension: 1D uses 1 and 2, 2D uses 2 and 3, 3D uses 3 and 4, 4D uses
38// 4 and 5. "Arbitrary seeds ... for compatibility with shading functions",
39// and there is no rule to derive them from.
3040
3141#include "blender_noise.h"
3242
……@@ -34,8 +44,6 @@
3444#include <godot_cpp/variant/vector3.hpp>
3545#include <godot_cpp/variant/vector4.hpp>
3646
37#include <cmath>
38
3947namespace godot {
4048namespace bperlin {
4149
……@@ -49,423 +57,118 @@ enum {
4957 PERLIN_HETERO_TERRAIN = 4,
5058};
5159
52// Blender's mix: (1 - x) * v0 + x * v1.
53static inline float pmix(float v0, float v1, float x) { return (1.0f - x) * v0 + x * v1; }
54
55static inline float pmix(float v0, float v1, float v2, float v3, float x, float y) {
56 float x1 = 1.0f - x;
57 return (1.0f - y) * (v0 * x1 + v1 * x) + y * (v2 * x1 + v3 * x);
58}
59
60static inline float pmix(float v0, float v1, float v2, float v3, float v4, float v5, float v6,
61 float v7, float x, float y, float z) {
62 float x1 = 1.0f - x;
63 float y1 = 1.0f - y;
64 float z1 = 1.0f - z;
65 return z1 * (y1 * (v0 * x1 + v1 * x) + y * (v2 * x1 + v3 * x)) +
66 z * (y1 * (v4 * x1 + v5 * x) + y * (v6 * x1 + v7 * x));
67}
68
69static inline float pmix(float v0, float v1, float v2, float v3, float v4, float v5, float v6,
70 float v7, float v8, float v9, float v10, float v11, float v12, float v13, float v14,
71 float v15, float x, float y, float z, float w) {
72 return pmix(pmix(v0, v1, v2, v3, v4, v5, v6, v7, x, y, z),
73 pmix(v8, v9, v10, v11, v12, v13, v14, v15, x, y, z), w);
74}
75
76static inline float fade(float t) { return t * t * t * (t * (t * 6.0f - 15.0f) + 10.0f); }
77
78static inline float negate_if(float value, uint32_t condition) {
79 return (condition != 0u) ? -value : value;
80}
81
82static inline float noise_grad(uint32_t hash_v, float x) {
83 uint32_t h = hash_v & 15u;
84 float g = 1.0f + (float)(h & 7u);
85 return negate_if(g, h & 8u) * x;
86}
87
88static inline float noise_grad(uint32_t hash_v, float x, float y) {
89 uint32_t h = hash_v & 7u;
90 float u = h < 4u ? x : y;
91 float v = 2.0f * (h < 4u ? y : x);
92 return negate_if(u, h & 1u) + negate_if(v, h & 2u);
93}
94
95static inline float noise_grad(uint32_t hash_v, float x, float y, float z) {
96 uint32_t h = hash_v & 15u;
97 float u = h < 8u ? x : y;
98 float vt = (h == 12u || h == 14u) ? x : z;
99 float v = h < 4u ? y : vt;
100 return negate_if(u, h & 1u) + negate_if(v, h & 2u);
101}
102
103static inline float noise_grad(uint32_t hash_v, float x, float y, float z, float w) {
104 uint32_t h = hash_v & 31u;
105 float u = h < 24u ? x : y;
106 float v = h < 16u ? y : z;
107 float s = h < 8u ? z : w;
108 return negate_if(u, h & 1u) + negate_if(v, h & 2u) + negate_if(s, h & 4u);
109}
110
111static inline float floor_fraction(float x, int &i) {
112 float x_floor = floorf(x);
113 i = (int)x_floor;
114 return x - x_floor;
115}
116
117static inline float perlin_noise(float position) {
118 int X;
119 float fx = floor_fraction(position, X);
120 float u = fade(fx);
121 return pmix(noise_grad(hash((uint32_t)X), fx),
122 noise_grad(hash((uint32_t)(X + 1)), fx - 1.0f), u);
123}
124
125static inline float perlin_noise(const Vector2 &position) {
126 int X, Y;
127 float fx = floor_fraction(position.x, X);
128 float fy = floor_fraction(position.y, Y);
129 float u = fade(fx);
130 float v = fade(fy);
131 return pmix(noise_grad(hash((uint32_t)X, (uint32_t)Y), fx, fy),
132 noise_grad(hash((uint32_t)(X + 1), (uint32_t)Y), fx - 1.0f, fy),
133 noise_grad(hash((uint32_t)X, (uint32_t)(Y + 1)), fx, fy - 1.0f),
134 noise_grad(hash((uint32_t)(X + 1), (uint32_t)(Y + 1)), fx - 1.0f, fy - 1.0f), u, v);
135}
136
137static inline float perlin_noise(const Vector3 &position) {
138 int X, Y, Z;
139 float fx = floor_fraction(position.x, X);
140 float fy = floor_fraction(position.y, Y);
141 float fz = floor_fraction(position.z, Z);
142 float u = fade(fx);
143 float v = fade(fy);
144 float w = fade(fz);
145 const uint32_t x0 = (uint32_t)X, x1 = (uint32_t)(X + 1);
146 const uint32_t y0 = (uint32_t)Y, y1 = (uint32_t)(Y + 1);
147 const uint32_t z0 = (uint32_t)Z, z1 = (uint32_t)(Z + 1);
148 return pmix(noise_grad(hash(x0, y0, z0), fx, fy, fz),
149 noise_grad(hash(x1, y0, z0), fx - 1.0f, fy, fz),
150 noise_grad(hash(x0, y1, z0), fx, fy - 1.0f, fz),
151 noise_grad(hash(x1, y1, z0), fx - 1.0f, fy - 1.0f, fz),
152 noise_grad(hash(x0, y0, z1), fx, fy, fz - 1.0f),
153 noise_grad(hash(x1, y0, z1), fx - 1.0f, fy, fz - 1.0f),
154 noise_grad(hash(x0, y1, z1), fx, fy - 1.0f, fz - 1.0f),
155 noise_grad(hash(x1, y1, z1), fx - 1.0f, fy - 1.0f, fz - 1.0f), u, v, w);
156}
157
158static inline float perlin_noise(const Vector4 &position) {
159 int X, Y, Z, W;
160 float fx = floor_fraction(position.x, X);
161 float fy = floor_fraction(position.y, Y);
162 float fz = floor_fraction(position.z, Z);
163 float fw = floor_fraction(position.w, W);
164 float u = fade(fx);
165 float v = fade(fy);
166 float t = fade(fz);
167 float s = fade(fw);
168 const uint32_t x0 = (uint32_t)X, x1 = (uint32_t)(X + 1);
169 const uint32_t y0 = (uint32_t)Y, y1 = (uint32_t)(Y + 1);
170 const uint32_t z0 = (uint32_t)Z, z1 = (uint32_t)(Z + 1);
171 const uint32_t w0 = (uint32_t)W, w1 = (uint32_t)(W + 1);
172 return pmix(noise_grad(hash(x0, y0, z0, w0), fx, fy, fz, fw),
173 noise_grad(hash(x1, y0, z0, w0), fx - 1.0f, fy, fz, fw),
174 noise_grad(hash(x0, y1, z0, w0), fx, fy - 1.0f, fz, fw),
175 noise_grad(hash(x1, y1, z0, w0), fx - 1.0f, fy - 1.0f, fz, fw),
176 noise_grad(hash(x0, y0, z1, w0), fx, fy, fz - 1.0f, fw),
177 noise_grad(hash(x1, y0, z1, w0), fx - 1.0f, fy, fz - 1.0f, fw),
178 noise_grad(hash(x0, y1, z1, w0), fx, fy - 1.0f, fz - 1.0f, fw),
179 noise_grad(hash(x1, y1, z1, w0), fx - 1.0f, fy - 1.0f, fz - 1.0f, fw),
180 noise_grad(hash(x0, y0, z0, w1), fx, fy, fz, fw - 1.0f),
181 noise_grad(hash(x1, y0, z0, w1), fx - 1.0f, fy, fz, fw - 1.0f),
182 noise_grad(hash(x0, y1, z0, w1), fx, fy - 1.0f, fz, fw - 1.0f),
183 noise_grad(hash(x1, y1, z0, w1), fx - 1.0f, fy - 1.0f, fz, fw - 1.0f),
184 noise_grad(hash(x0, y0, z1, w1), fx, fy, fz - 1.0f, fw - 1.0f),
185 noise_grad(hash(x1, y0, z1, w1), fx - 1.0f, fy, fz - 1.0f, fw - 1.0f),
186 noise_grad(hash(x0, y1, z1, w1), fx, fy - 1.0f, fz - 1.0f, fw - 1.0f),
187 noise_grad(hash(x1, y1, z1, w1), fx - 1.0f, fy - 1.0f, fz - 1.0f, fw - 1.0f),
188 u, v, t, s);
189}
190
191// The scale constants were found experimentally by the OSL developers to remap
192// each dimension's output to [-1, 1]. They differ per dimension and cannot be
193// derived; 0.9820 is the 3D one.
194//
195// The modulo repeats the texture every 100000 on each axis to keep the lattice
196// coordinates representable, with a half-unit nudge past a million so the two
197// sides of the wrap do not land on the same lattice point.
198static inline float perlin_signed(float position) {
199 float precision_correction = 0.5f * (float)(fabsf(position) >= 1000000.0f);
200 position = fmodf(position, 100000.0f) + precision_correction;
201 return perlin_noise(position) * 0.2500f;
202}
203
204static inline float perlin_signed(Vector2 position) {
205 Vector2 pc(0.5f * (float)(fabsf(position.x) >= 1000000.0f),
206 0.5f * (float)(fabsf(position.y) >= 1000000.0f));
207 position = Vector2(fmodf(position.x, 100000.0f), fmodf(position.y, 100000.0f)) + pc;
208 return perlin_noise(position) * 0.6616f;
209}
210
211static inline float perlin_signed(Vector3 position) {
212 Vector3 pc(0.5f * (float)(fabsf(position.x) >= 1000000.0f),
213 0.5f * (float)(fabsf(position.y) >= 1000000.0f),
214 0.5f * (float)(fabsf(position.z) >= 1000000.0f));
215 position = Vector3(fmodf(position.x, 100000.0f), fmodf(position.y, 100000.0f),
216 fmodf(position.z, 100000.0f)) +
217 pc;
218 return perlin_noise(position) * 0.9820f;
219}
220
221static inline float perlin_signed(Vector4 position) {
222 Vector4 pc(0.5f * (float)(fabsf(position.x) >= 1000000.0f),
223 0.5f * (float)(fabsf(position.y) >= 1000000.0f),
224 0.5f * (float)(fabsf(position.z) >= 1000000.0f),
225 0.5f * (float)(fabsf(position.w) >= 1000000.0f));
226 position = Vector4(fmodf(position.x, 100000.0f), fmodf(position.y, 100000.0f),
227 fmodf(position.z, 100000.0f), fmodf(position.w, 100000.0f)) +
228 pc;
229 return perlin_noise(position) * 0.8344f;
230}
231
232/* Fractal perlin noise. */
60namespace detail {
61inline bnoisegl::vec2 gl(const Vector2 &v) { return bnoisegl::vec2(v.x, v.y); }
62inline bnoisegl::vec3 gl(const Vector3 &v) { return bnoisegl::vec3(v.x, v.y, v.z); }
63inline bnoisegl::vec4 gl(const Vector4 &v) { return bnoisegl::vec4(v.x, v.y, v.z, v.w); }
64inline Vector3 gd(const bnoisegl::vec3 &v) { return Vector3(v.x, v.y, v.z); }
65} // namespace detail
23366
234template <typename T>
235static float perlin_fbm(T p, const float detail, const float roughness, const float lacunarity,
236 const bool normalize) {
237 float fscale = 1.0f;
238 float amp = 1.0f;
239 float maxamp = 0.0f;
240 float sum = 0.0f;
241
242 for (int i = 0; i <= (int)detail; i++) {
243 float t = perlin_signed(p * fscale);
244 sum += t * amp;
245 maxamp += amp;
246 amp *= roughness;
247 fscale *= lacunarity;
248 }
249 float rmd = detail - floorf(detail);
250 if (rmd != 0.0f) {
251 float t = perlin_signed(p * fscale);
252 float sum2 = sum + t * amp;
253 return normalize ? pmix(0.5f * sum / maxamp + 0.5f, 0.5f * sum2 / (maxamp + amp) + 0.5f, rmd)
254 : pmix(sum, sum2, rmd);
255 }
256 return normalize ? 0.5f * sum / maxamp + 0.5f : sum;
257}
67// --- the fractal families, dispatched by the caller's coordinate type ---
25868
25969template <typename T>
260static float perlin_multi_fractal(T p, const float detail, const float roughness,
261 const float lacunarity) {
262 float value = 1.0f;
263 float pwr = 1.0f;
264
265 for (int i = 0; i <= (int)detail; i++) {
266 value *= (pwr * perlin_signed(p) + 1.0f);
267 pwr *= roughness;
268 p = p * lacunarity;
269 }
70float perlin_fbm(T p, float detail_v, float roughness, float lacunarity, bool normalize);
27071
271 const float rmd = detail - floorf(detail);
272 if (rmd != 0.0f) {
273 value *= (rmd * pwr * perlin_signed(p) + 1.0f);
274 }
275
276 return value;
72template <>
73inline float perlin_fbm<float>(float p, float d, float r, float l, bool n) {
74 return bnoisegl::bp_perlin_fbm_1(p, d, r, l, n);
27775}
278
279template <typename T>
280static float perlin_hetero_terrain(T p, const float detail, const float roughness,
281 const float lacunarity, const float offset) {
282 float pwr = roughness;
283
284 /* First unscaled octave of function; later octaves are scaled. */
285 float value = offset + perlin_signed(p);
286 p = p * lacunarity;
287
288 for (int i = 1; i <= (int)detail; i++) {
289 float increment = (perlin_signed(p) + offset) * pwr * value;
290 value += increment;
291 pwr *= roughness;
292 p = p * lacunarity;
293 }
294
295 const float rmd = detail - floorf(detail);
296 if (rmd != 0.0f) {
297 float increment = (perlin_signed(p) + offset) * pwr * value;
298 value += rmd * increment;
299 }
300
301 return value;
76template <>
77inline float perlin_fbm<Vector2>(Vector2 p, float d, float r, float l, bool n) {
78 return bnoisegl::bp_perlin_fbm_2(detail::gl(p), d, r, l, n);
30279}
303
304template <typename T>
305static float perlin_hybrid_multi_fractal(T p, const float detail, const float roughness,
306 const float lacunarity, const float offset, const float gain) {
307 float pwr = 1.0f;
308 float value = 0.0f;
309 float weight = 1.0f;
310
311 for (int i = 0; (weight > 0.001f) && (i <= (int)detail); i++) {
312 weight = MIN(weight, 1.0f);
313
314 float signal = (perlin_signed(p) + offset) * pwr;
315 pwr *= roughness;
316 value += weight * signal;
317 weight *= gain * signal;
318 p = p * lacunarity;
319 }
320
321 const float rmd = detail - floorf(detail);
322 if ((rmd != 0.0f) && (weight > 0.001f)) {
323 weight = MIN(weight, 1.0f);
324 float signal = (perlin_signed(p) + offset) * pwr;
325 value += rmd * weight * signal;
326 }
327
328 return value;
80template <>
81inline float perlin_fbm<Vector3>(Vector3 p, float d, float r, float l, bool n) {
82 return bnoisegl::bp_perlin_fbm_3(detail::gl(p), d, r, l, n);
32983}
330
331template <typename T>
332static float perlin_ridged_multi_fractal(T p, const float detail, const float roughness,
333 const float lacunarity, const float offset, const float gain) {
334 float pwr = roughness;
335
336 float signal = offset - fabsf(perlin_signed(p));
337 signal *= signal;
338 float value = signal;
339 float weight = 1.0f;
340
341 for (int i = 1; i <= (int)detail; i++) {
342 p = p * lacunarity;
343 weight = CLAMP(signal * gain, 0.0f, 1.0f);
344 signal = offset - fabsf(perlin_signed(p));
345 signal *= signal;
346 signal *= weight;
347 value += signal * pwr;
348 pwr *= roughness;
349 }
350
351 return value;
84template <>
85inline float perlin_fbm<Vector4>(Vector4 p, float d, float r, float l, bool n) {
86 return bnoisegl::bp_perlin_fbm_4(detail::gl(p), d, r, l, n);
35287}
35388
35489template <typename T>
355static float perlin_select(T p, float detail, float roughness, float lacunarity, float offset,
356 float gain, int type, bool normalize) {
357 switch (type) {
358 case PERLIN_MULTIFRACTAL:
359 return perlin_multi_fractal<T>(p, detail, roughness, lacunarity);
360 case PERLIN_FBM:
361 return perlin_fbm<T>(p, detail, roughness, lacunarity, normalize);
362 case PERLIN_HYBRID_MULTIFRACTAL:
363 return perlin_hybrid_multi_fractal<T>(p, detail, roughness, lacunarity, offset, gain);
364 case PERLIN_RIDGED_MULTIFRACTAL:
365 return perlin_ridged_multi_fractal<T>(p, detail, roughness, lacunarity, offset, gain);
366 case PERLIN_HETERO_TERRAIN:
367 return perlin_hetero_terrain<T>(p, detail, roughness, lacunarity, offset);
368 }
369 return 0.0f;
370}
371
372// A float seed rather than an integer one, because OSL only has float hashes
373// and this has to stay compatible with it.
374static inline float random_float_offset(float seed) {
375 return 100.0f + hash_float_to_float(seed) * 100.0f;
376}
377static inline Vector2 random_float2_offset(float seed) {
378 return Vector2(100.0f + hash_float_to_float(Vector2(seed, 0.0f)) * 100.0f,
379 100.0f + hash_float_to_float(Vector2(seed, 1.0f)) * 100.0f);
380}
381static inline Vector3 random_float3_offset(float seed) {
382 return Vector3(100.0f + hash_float_to_float(Vector2(seed, 0.0f)) * 100.0f,
383 100.0f + hash_float_to_float(Vector2(seed, 1.0f)) * 100.0f,
384 100.0f + hash_float_to_float(Vector2(seed, 2.0f)) * 100.0f);
385}
386static inline Vector4 random_float4_offset(float seed) {
387 return Vector4(100.0f + hash_float_to_float(Vector2(seed, 0.0f)) * 100.0f,
388 100.0f + hash_float_to_float(Vector2(seed, 1.0f)) * 100.0f,
389 100.0f + hash_float_to_float(Vector2(seed, 2.0f)) * 100.0f,
390 100.0f + hash_float_to_float(Vector2(seed, 3.0f)) * 100.0f);
391}
90float perlin_select(T p, float detail_v, float roughness, float lacunarity, float offset,
91 float gain, int type, bool normalize);
39292
393static inline float perlin_distortion(float position, float strength) {
394 return perlin_signed(position + random_float_offset(0.0f)) * strength;
93template <>
94inline float perlin_select<float>(float p, float d, float r, float l, float o, float g, int t,
95 bool n) {
96 return bnoisegl::bp_perlin_select_1(p, d, r, l, o, g, t, n);
39597}
396static inline Vector2 perlin_distortion(const Vector2 &position, float strength) {
397 return Vector2(perlin_signed(position + random_float2_offset(0.0f)) * strength,
398 perlin_signed(position + random_float2_offset(1.0f)) * strength);
98template <>
99inline float perlin_select<Vector2>(Vector2 p, float d, float r, float l, float o, float g, int t,
100 bool n) {
101 return bnoisegl::bp_perlin_select_2(detail::gl(p), d, r, l, o, g, t, n);
399102}
400static inline Vector3 perlin_distortion(const Vector3 &position, float strength) {
401 return Vector3(perlin_signed(position + random_float3_offset(0.0f)) * strength,
402 perlin_signed(position + random_float3_offset(1.0f)) * strength,
403 perlin_signed(position + random_float3_offset(2.0f)) * strength);
103template <>
104inline float perlin_select<Vector3>(Vector3 p, float d, float r, float l, float o, float g, int t,
105 bool n) {
106 return bnoisegl::bp_perlin_select_3(detail::gl(p), d, r, l, o, g, t, n);
404107}
405static inline Vector4 perlin_distortion(const Vector4 &position, float strength) {
406 return Vector4(perlin_signed(position + random_float4_offset(0.0f)) * strength,
407 perlin_signed(position + random_float4_offset(1.0f)) * strength,
408 perlin_signed(position + random_float4_offset(2.0f)) * strength,
409 perlin_signed(position + random_float4_offset(3.0f)) * strength);
108template <>
109inline float perlin_select<Vector4>(Vector4 p, float d, float r, float l, float o, float g, int t,
110 bool n) {
111 return bnoisegl::bp_perlin_select_4(detail::gl(p), d, r, l, o, g, t, n);
410112}
411113
412114template <typename T>
413static float perlin_fractal_distorted(T position, float detail, float roughness, float lacunarity,
414 float offset, float gain, float distortion, int type, bool normalize) {
415 position = position + perlin_distortion(position, distortion);
416 return perlin_select<T>(position, detail, roughness, lacunarity, offset, gain, type, normalize);
417}
418
419// THE COLOUR SEEDS DIFFER PER DIMENSION: 1D uses 1 and 2, 2D uses 2 and 3, 3D
420// uses 3 and 4, 4D uses 4 and 5. Blender calls them "arbitrary seeds ... for
421// compatibility with shading functions" and there is no rule behind them.
422static inline Vector3 perlin_float3_fractal_distorted(float position, float detail,
423 float roughness, float lacunarity, float offset, float gain, float distortion, int type,
424 bool normalize) {
425 position += perlin_distortion(position, distortion);
426 return Vector3(
427 perlin_select<float>(position, detail, roughness, lacunarity, offset, gain, type, normalize),
428 perlin_select<float>(position + random_float_offset(1.0f), detail, roughness,
429 lacunarity, offset, gain, type, normalize),
430 perlin_select<float>(position + random_float_offset(2.0f), detail, roughness,
431 lacunarity, offset, gain, type, normalize));
432}
433
434static inline Vector3 perlin_float3_fractal_distorted(Vector2 position, float detail,
435 float roughness, float lacunarity, float offset, float gain, float distortion, int type,
436 bool normalize) {
437 position = position + perlin_distortion(position, distortion);
438 return Vector3(
439 perlin_select<Vector2>(position, detail, roughness, lacunarity, offset, gain, type, normalize),
440 perlin_select<Vector2>(position + random_float2_offset(2.0f), detail, roughness,
441 lacunarity, offset, gain, type, normalize),
442 perlin_select<Vector2>(position + random_float2_offset(3.0f), detail, roughness,
443 lacunarity, offset, gain, type, normalize));
444}
445
446static inline Vector3 perlin_float3_fractal_distorted(Vector3 position, float detail,
447 float roughness, float lacunarity, float offset, float gain, float distortion, int type,
448 bool normalize) {
449 position = position + perlin_distortion(position, distortion);
450 return Vector3(
451 perlin_select<Vector3>(position, detail, roughness, lacunarity, offset, gain, type, normalize),
452 perlin_select<Vector3>(position + random_float3_offset(3.0f), detail, roughness,
453 lacunarity, offset, gain, type, normalize),
454 perlin_select<Vector3>(position + random_float3_offset(4.0f), detail, roughness,
455 lacunarity, offset, gain, type, normalize));
456}
457
458static inline Vector3 perlin_float3_fractal_distorted(Vector4 position, float detail,
459 float roughness, float lacunarity, float offset, float gain, float distortion, int type,
460 bool normalize) {
461 position = position + perlin_distortion(position, distortion);
462 return Vector3(
463 perlin_select<Vector4>(position, detail, roughness, lacunarity, offset, gain, type, normalize),
464 perlin_select<Vector4>(position + random_float4_offset(4.0f), detail, roughness,
465 lacunarity, offset, gain, type, normalize),
466 perlin_select<Vector4>(position + random_float4_offset(5.0f), detail, roughness,
467 lacunarity, offset, gain, type, normalize));
468}
115float perlin_fractal_distorted(T p, float detail_v, float roughness, float lacunarity,
116 float offset, float gain, float distortion, int type, bool normalize);
117
118template <>
119inline float perlin_fractal_distorted<float>(float p, float d, float r, float l, float o, float g,
120 float dist, int t, bool n) {
121 return bnoisegl::bp_perlin_fractal_distorted_1(p, d, r, l, o, g, dist, t, n);
122}
123template <>
124inline float perlin_fractal_distorted<Vector2>(Vector2 p, float d, float r, float l, float o,
125 float g, float dist, int t, bool n) {
126 return bnoisegl::bp_perlin_fractal_distorted_2(detail::gl(p), d, r, l, o, g, dist, t, n);
127}
128template <>
129inline float perlin_fractal_distorted<Vector3>(Vector3 p, float d, float r, float l, float o,
130 float g, float dist, int t, bool n) {
131 return bnoisegl::bp_perlin_fractal_distorted_3(detail::gl(p), d, r, l, o, g, dist, t, n);
132}
133template <>
134inline float perlin_fractal_distorted<Vector4>(Vector4 p, float d, float r, float l, float o,
135 float g, float dist, int t, bool n) {
136 return bnoisegl::bp_perlin_fractal_distorted_4(detail::gl(p), d, r, l, o, g, dist, t, n);
137}
138
139// The Colour output. Overloads rather than specialisations, because that is
140// what the callers write and what Blender's own source does.
141inline Vector3 perlin_float3_fractal_distorted(float p, float d, float r, float l, float o,
142 float g, float dist, int t, bool n) {
143 return detail::gd(bnoisegl::bp_perlin_float3_fractal_distorted(p, d, r, l, o, g, dist, t, n));
144}
145inline Vector3 perlin_float3_fractal_distorted(Vector2 p, float d, float r, float l, float o,
146 float g, float dist, int t, bool n) {
147 return detail::gd(
148 bnoisegl::bp_perlin_float3_fractal_distorted(detail::gl(p), d, r, l, o, g, dist, t, n));
149}
150inline Vector3 perlin_float3_fractal_distorted(Vector3 p, float d, float r, float l, float o,
151 float g, float dist, int t, bool n) {
152 return detail::gd(
153 bnoisegl::bp_perlin_float3_fractal_distorted(detail::gl(p), d, r, l, o, g, dist, t, n));
154}
155inline Vector3 perlin_float3_fractal_distorted(Vector4 p, float d, float r, float l, float o,
156 float g, float dist, int t, bool n) {
157 return detail::gd(
158 bnoisegl::bp_perlin_float3_fractal_distorted(detail::gl(p), d, r, l, o, g, dist, t, n));
159}
160
161// --- the layers below, for probes and gates ---
162
163inline float perlin_noise(float p) { return bnoisegl::bp_perlin_noise(p); }
164inline float perlin_noise(const Vector2 &p) { return bnoisegl::bp_perlin_noise(detail::gl(p)); }
165inline float perlin_noise(const Vector3 &p) { return bnoisegl::bp_perlin_noise(detail::gl(p)); }
166inline float perlin_noise(const Vector4 &p) { return bnoisegl::bp_perlin_noise(detail::gl(p)); }
167
168inline float perlin_signed(float p) { return bnoisegl::bp_perlin_signed(p); }
169inline float perlin_signed(const Vector2 &p) { return bnoisegl::bp_perlin_signed(detail::gl(p)); }
170inline float perlin_signed(const Vector3 &p) { return bnoisegl::bp_perlin_signed(detail::gl(p)); }
171inline float perlin_signed(const Vector4 &p) { return bnoisegl::bp_perlin_signed(detail::gl(p)); }
469172
470173} // namespace bperlin
471174} // namespace godot
src/shared/blender_perlin.inc +489 -0
……@@ -0,0 +1,489 @@
1// ============================================================================
2// BLENDER'S PERLIN NOISE -- ONE TEXT, COMPILED AS BOTH GLSL AND C++.
3//
4// From the "Perlin Noise" section of
5// `source/blender/blenlib/intern/noise.cc`, Blender source commit 61a36e08,
6// BLENDER_VERSION 502. Blender's own header notes it is functionally identical
7// to the EEVEE, OSL and SVM implementations, so any change belongs in all of
8// them; here it needs to stay identical to Blender's, which is the point.
9//
10// DEPENDS ON blender_hash.inc and does not include it: GLSL has no #include,
11// so the emitter concatenates hash-then-perlin and the C++ shim includes them
12// in that order. Perlin's lattice gradients ARE that hash.
13//
14// Two things in here could not have been guessed:
15//
16// the per-dimension SCALE CONSTANTS on perlin_signed -- 0.2500, 0.6616,
17// 0.9820, 0.8344 -- which the OSL developers found experimentally to remap
18// each dimension's output to [-1, 1]. 0.9820 is the 3D one, and it is the
19// same 0.982 that appeared as an unexplained "normalization factor matching
20// iq/Blender" in ProcNodes_Noised, where it did not belong.
21//
22// the SEEDS of the Colour output's second and third components, which differ
23// per dimension: 1D uses offsets 1 and 2, 2D uses 2 and 3, 3D uses 3 and 4,
24// 4D uses 4 and 5. Blender calls them "arbitrary seeds ... for compatibility
25// with shading functions", and there is no rule to derive them from.
26//
27// ---------------------------------------------------------------------------
28// THE SUBSET, AND THE THREE WAYS OUT OF IT
29//
30// 1. C++ TEMPLATES. Five of these functions are `template <typename T>` and
31// GLSL has none, so each becomes four per-dimension copies. Writing those
32// twenty bodies by hand is exactly the transcription this file exists to
33// avoid -- one wrong suffix and a dimension silently evaluates another
34// one -- so the body is written ONCE and the preprocessor expands it,
35// with `##` for the suffix. Verified to compile under glslc/glslang,
36// which is what both the gate and Godot's RenderingDevice use. The
37// functions that are OVERLOADS in Blender's C++ stay overloads here; only
38// the templates are macros, so the structure still matches the source.
39//
40// 2. THE REFERENCE OUT-PARAMETER. floor_fraction returned the fraction and
41// wrote the integer through `int &`. GLSL's `inout` is not the same
42// thing, so it is two functions over the same floor().
43//
44// 3. fmodf IS TRUNCATED AND GLSL's mod() IS FLOORED. They differ for
45// negative operands, which perlin_signed feeds constantly. bp_fmod
46// spells out the truncated form; writing mod() would wrap the texture
47// differently on the two sides of the origin.
48//
49// ⚠ EVERY DECIMAL LITERAL CARRIES AN `f`. Without it, `0.9820` is a float in
50// GLSL and a DOUBLE in C++, so the CPU would compute an octave in double and
51// the GPU in float. GLSL accepts the suffix (measured, glslc 2023.8) and it
52// makes both languages parse the same float. The four constants that are not
53// exactly representable -- 0.6616, 0.9820, 0.8344, 0.001 -- happen to round
54// the same either way, measured, but relying on that is a coin the next
55// constant need not land on.
56// ============================================================================
57
58#ifndef PN_BLENDER_PERLIN
59#define PN_BLENDER_PERLIN
60
61#ifndef BN_FN
62#define BN_FN
63#endif
64
65const int BP_MULTIFRACTAL = 0;
66const int BP_FBM = 1;
67const int BP_HYBRID_MULTIFRACTAL = 2;
68const int BP_RIDGED_MULTIFRACTAL = 3;
69const int BP_HETERO_TERRAIN = 4;
70
71// Blender's mix: (1 - x) * v0 + x * v1. NOT GLSL's mix(), which is
72// v0 + x * (v1 - v0) -- algebraically the same and not the same in floating
73// point, and this is the interpolation every octave passes through.
74BN_FN float bp_pmix(float v0, float v1, float x) { return (1.0f - x) * v0 + x * v1; }
75
76BN_FN float bp_pmix(float v0, float v1, float v2, float v3, float x, float y) {
77 float x1 = 1.0f - x;
78 return (1.0f - y) * (v0 * x1 + v1 * x) + y * (v2 * x1 + v3 * x);
79}
80
81BN_FN float bp_pmix(float v0, float v1, float v2, float v3, float v4, float v5, float v6,
82 float v7, float x, float y, float z) {
83 float x1 = 1.0f - x;
84 float y1 = 1.0f - y;
85 float z1 = 1.0f - z;
86 return z1 * (y1 * (v0 * x1 + v1 * x) + y * (v2 * x1 + v3 * x)) +
87 z * (y1 * (v4 * x1 + v5 * x) + y * (v6 * x1 + v7 * x));
88}
89
90BN_FN float bp_pmix(float v0, float v1, float v2, float v3, float v4, float v5, float v6,
91 float v7, float v8, float v9, float v10, float v11, float v12, float v13, float v14,
92 float v15, float x, float y, float z, float w) {
93 return bp_pmix(bp_pmix(v0, v1, v2, v3, v4, v5, v6, v7, x, y, z),
94 bp_pmix(v8, v9, v10, v11, v12, v13, v14, v15, x, y, z), w);
95}
96
97BN_FN float bp_fade(float t) { return t * t * t * (t * (t * 6.0f - 15.0f) + 10.0f); }
98
99BN_FN float bp_negate_if(float value, uint condition) {
100 return (condition != 0u) ? -value : value;
101}
102
103BN_FN float bp_noise_grad(uint hash_v, float x) {
104 uint h = hash_v & 15u;
105 float g = 1.0f + float(h & 7u);
106 return bp_negate_if(g, h & 8u) * x;
107}
108
109BN_FN float bp_noise_grad(uint hash_v, float x, float y) {
110 uint h = hash_v & 7u;
111 float u = h < 4u ? x : y;
112 float v = 2.0f * (h < 4u ? y : x);
113 return bp_negate_if(u, h & 1u) + bp_negate_if(v, h & 2u);
114}
115
116BN_FN float bp_noise_grad(uint hash_v, float x, float y, float z) {
117 uint h = hash_v & 15u;
118 float u = h < 8u ? x : y;
119 float vt = (h == 12u || h == 14u) ? x : z;
120 float v = h < 4u ? y : vt;
121 return bp_negate_if(u, h & 1u) + bp_negate_if(v, h & 2u);
122}
123
124BN_FN float bp_noise_grad(uint hash_v, float x, float y, float z, float w) {
125 uint h = hash_v & 31u;
126 float u = h < 24u ? x : y;
127 float v = h < 16u ? y : z;
128 float s = h < 8u ? z : w;
129 return bp_negate_if(u, h & 1u) + bp_negate_if(v, h & 2u) + bp_negate_if(s, h & 4u);
130}
131
132// floor_fraction, split because it wrote its integer through a reference.
133BN_FN int bp_floor_int(float x) { return int(floor(x)); }
134BN_FN float bp_floor_fract(float x) { return x - floor(x); }
135
136// TRUNCATED, like fmodf. GLSL's mod() is floored and would wrap the texture
137// differently either side of the origin.
138BN_FN float bp_fmod(float a, float b) { return a - b * trunc(a / b); }
139
140BN_FN float bp_perlin_noise(float position) {
141 int X = bp_floor_int(position);
142 float fx = bp_floor_fract(position);
143 float u = bp_fade(fx);
144 return bp_pmix(bp_noise_grad(bn_hash(uint(X)), fx),
145 bp_noise_grad(bn_hash(uint(X + 1)), fx - 1.0f), u);
146}
147
148BN_FN float bp_perlin_noise(vec2 position) {
149 int X = bp_floor_int(position.x);
150 int Y = bp_floor_int(position.y);
151 float fx = bp_floor_fract(position.x);
152 float fy = bp_floor_fract(position.y);
153 float u = bp_fade(fx);
154 float v = bp_fade(fy);
155 return bp_pmix(bp_noise_grad(bn_hash(uint(X), uint(Y)), fx, fy),
156 bp_noise_grad(bn_hash(uint(X + 1), uint(Y)), fx - 1.0f, fy),
157 bp_noise_grad(bn_hash(uint(X), uint(Y + 1)), fx, fy - 1.0f),
158 bp_noise_grad(bn_hash(uint(X + 1), uint(Y + 1)), fx - 1.0f, fy - 1.0f), u, v);
159}
160
161BN_FN float bp_perlin_noise(vec3 position) {
162 int X = bp_floor_int(position.x);
163 int Y = bp_floor_int(position.y);
164 int Z = bp_floor_int(position.z);
165 float fx = bp_floor_fract(position.x);
166 float fy = bp_floor_fract(position.y);
167 float fz = bp_floor_fract(position.z);
168 float u = bp_fade(fx);
169 float v = bp_fade(fy);
170 float w = bp_fade(fz);
171 uint x0 = uint(X); uint x1 = uint(X + 1);
172 uint y0 = uint(Y); uint y1 = uint(Y + 1);
173 uint z0 = uint(Z); uint z1 = uint(Z + 1);
174 return bp_pmix(bp_noise_grad(bn_hash(x0, y0, z0), fx, fy, fz),
175 bp_noise_grad(bn_hash(x1, y0, z0), fx - 1.0f, fy, fz),
176 bp_noise_grad(bn_hash(x0, y1, z0), fx, fy - 1.0f, fz),
177 bp_noise_grad(bn_hash(x1, y1, z0), fx - 1.0f, fy - 1.0f, fz),
178 bp_noise_grad(bn_hash(x0, y0, z1), fx, fy, fz - 1.0f),
179 bp_noise_grad(bn_hash(x1, y0, z1), fx - 1.0f, fy, fz - 1.0f),
180 bp_noise_grad(bn_hash(x0, y1, z1), fx, fy - 1.0f, fz - 1.0f),
181 bp_noise_grad(bn_hash(x1, y1, z1), fx - 1.0f, fy - 1.0f, fz - 1.0f), u, v, w);
182}
183
184BN_FN float bp_perlin_noise(vec4 position) {
185 int X = bp_floor_int(position.x);
186 int Y = bp_floor_int(position.y);
187 int Z = bp_floor_int(position.z);
188 int W = bp_floor_int(position.w);
189 float fx = bp_floor_fract(position.x);
190 float fy = bp_floor_fract(position.y);
191 float fz = bp_floor_fract(position.z);
192 float fw = bp_floor_fract(position.w);
193 float u = bp_fade(fx);
194 float v = bp_fade(fy);
195 float t = bp_fade(fz);
196 float s = bp_fade(fw);
197 uint x0 = uint(X); uint x1 = uint(X + 1);
198 uint y0 = uint(Y); uint y1 = uint(Y + 1);
199 uint z0 = uint(Z); uint z1 = uint(Z + 1);
200 uint w0 = uint(W); uint w1 = uint(W + 1);
201 return bp_pmix(bp_noise_grad(bn_hash(x0, y0, z0, w0), fx, fy, fz, fw),
202 bp_noise_grad(bn_hash(x1, y0, z0, w0), fx - 1.0f, fy, fz, fw),
203 bp_noise_grad(bn_hash(x0, y1, z0, w0), fx, fy - 1.0f, fz, fw),
204 bp_noise_grad(bn_hash(x1, y1, z0, w0), fx - 1.0f, fy - 1.0f, fz, fw),
205 bp_noise_grad(bn_hash(x0, y0, z1, w0), fx, fy, fz - 1.0f, fw),
206 bp_noise_grad(bn_hash(x1, y0, z1, w0), fx - 1.0f, fy, fz - 1.0f, fw),
207 bp_noise_grad(bn_hash(x0, y1, z1, w0), fx, fy - 1.0f, fz - 1.0f, fw),
208 bp_noise_grad(bn_hash(x1, y1, z1, w0), fx - 1.0f, fy - 1.0f, fz - 1.0f, fw),
209 bp_noise_grad(bn_hash(x0, y0, z0, w1), fx, fy, fz, fw - 1.0f),
210 bp_noise_grad(bn_hash(x1, y0, z0, w1), fx - 1.0f, fy, fz, fw - 1.0f),
211 bp_noise_grad(bn_hash(x0, y1, z0, w1), fx, fy - 1.0f, fz, fw - 1.0f),
212 bp_noise_grad(bn_hash(x1, y1, z0, w1), fx - 1.0f, fy - 1.0f, fz, fw - 1.0f),
213 bp_noise_grad(bn_hash(x0, y0, z1, w1), fx, fy, fz - 1.0f, fw - 1.0f),
214 bp_noise_grad(bn_hash(x1, y0, z1, w1), fx - 1.0f, fy, fz - 1.0f, fw - 1.0f),
215 bp_noise_grad(bn_hash(x0, y1, z1, w1), fx, fy - 1.0f, fz - 1.0f, fw - 1.0f),
216 bp_noise_grad(bn_hash(x1, y1, z1, w1), fx - 1.0f, fy - 1.0f, fz - 1.0f, fw - 1.0f),
217 u, v, t, s);
218}
219
220// The modulo repeats the texture every 100000 on each axis to keep the lattice
221// coordinates representable, with a half-unit nudge past a million so the two
222// sides of the wrap do not land on the same lattice point.
223BN_FN float bp_perlin_signed(float position) {
224 float pc = 0.5f * float(abs(position) >= 1000000.0f);
225 float p = bp_fmod(position, 100000.0f) + pc;
226 return bp_perlin_noise(p) * 0.2500f;
227}
228
229BN_FN float bp_perlin_signed(vec2 position) {
230 vec2 pc = vec2(0.5f * float(abs(position.x) >= 1000000.0f),
231 0.5f * float(abs(position.y) >= 1000000.0f));
232 vec2 p = vec2(bp_fmod(position.x, 100000.0f), bp_fmod(position.y, 100000.0f)) + pc;
233 return bp_perlin_noise(p) * 0.6616f;
234}
235
236BN_FN float bp_perlin_signed(vec3 position) {
237 vec3 pc = vec3(0.5f * float(abs(position.x) >= 1000000.0f),
238 0.5f * float(abs(position.y) >= 1000000.0f),
239 0.5f * float(abs(position.z) >= 1000000.0f));
240 vec3 p = vec3(bp_fmod(position.x, 100000.0f), bp_fmod(position.y, 100000.0f),
241 bp_fmod(position.z, 100000.0f)) + pc;
242 return bp_perlin_noise(p) * 0.9820f;
243}
244
245BN_FN float bp_perlin_signed(vec4 position) {
246 vec4 pc = vec4(0.5f * float(abs(position.x) >= 1000000.0f),
247 0.5f * float(abs(position.y) >= 1000000.0f),
248 0.5f * float(abs(position.z) >= 1000000.0f),
249 0.5f * float(abs(position.w) >= 1000000.0f));
250 vec4 p = vec4(bp_fmod(position.x, 100000.0f), bp_fmod(position.y, 100000.0f),
251 bp_fmod(position.z, 100000.0f), bp_fmod(position.w, 100000.0f)) + pc;
252 return bp_perlin_noise(p) * 0.8344f;
253}
254
255// A float seed rather than an integer one, because OSL only has float hashes
256// and this has to stay compatible with it.
257BN_FN float bp_random_float_offset(float seed) {
258 return 100.0f + bn_hash_float_to_float(seed) * 100.0f;
259}
260BN_FN vec2 bp_random_float2_offset(float seed) {
261 return vec2(100.0f + bn_hash_float_to_float(vec2(seed, 0.0f)) * 100.0f,
262 100.0f + bn_hash_float_to_float(vec2(seed, 1.0f)) * 100.0f);
263}
264BN_FN vec3 bp_random_float3_offset(float seed) {
265 return vec3(100.0f + bn_hash_float_to_float(vec2(seed, 0.0f)) * 100.0f,
266 100.0f + bn_hash_float_to_float(vec2(seed, 1.0f)) * 100.0f,
267 100.0f + bn_hash_float_to_float(vec2(seed, 2.0f)) * 100.0f);
268}
269BN_FN vec4 bp_random_float4_offset(float seed) {
270 return vec4(100.0f + bn_hash_float_to_float(vec2(seed, 0.0f)) * 100.0f,
271 100.0f + bn_hash_float_to_float(vec2(seed, 1.0f)) * 100.0f,
272 100.0f + bn_hash_float_to_float(vec2(seed, 2.0f)) * 100.0f,
273 100.0f + bn_hash_float_to_float(vec2(seed, 3.0f)) * 100.0f);
274}
275
276BN_FN float bp_perlin_distortion(float position, float strength) {
277 return bp_perlin_signed(position + bp_random_float_offset(0.0f)) * strength;
278}
279BN_FN vec2 bp_perlin_distortion(vec2 position, float strength) {
280 return vec2(bp_perlin_signed(position + bp_random_float2_offset(0.0f)) * strength,
281 bp_perlin_signed(position + bp_random_float2_offset(1.0f)) * strength);
282}
283BN_FN vec3 bp_perlin_distortion(vec3 position, float strength) {
284 return vec3(bp_perlin_signed(position + bp_random_float3_offset(0.0f)) * strength,
285 bp_perlin_signed(position + bp_random_float3_offset(1.0f)) * strength,
286 bp_perlin_signed(position + bp_random_float3_offset(2.0f)) * strength);
287}
288BN_FN vec4 bp_perlin_distortion(vec4 position, float strength) {
289 return vec4(bp_perlin_signed(position + bp_random_float4_offset(0.0f)) * strength,
290 bp_perlin_signed(position + bp_random_float4_offset(1.0f)) * strength,
291 bp_perlin_signed(position + bp_random_float4_offset(2.0f)) * strength,
292 bp_perlin_signed(position + bp_random_float4_offset(3.0f)) * strength);
293}
294
295// ---------------------------------------------------------------------------
296// THE FIVE FRACTAL TYPES.
297//
298// `template <typename T>` in Blender's C++, and GLSL has no templates, so the
299// body is written once here and the preprocessor stamps it out per dimension.
300// T is the coordinate type, S the suffix -- bp_perlin_fbm_3 and so on.
301// ---------------------------------------------------------------------------
302
303#define BP_FRACTALS(T, S) \
304BN_FN float bp_perlin_fbm_##S(T p, float detail, float roughness, float lacunarity, \
305 bool normalize) { \
306 float fscale = 1.0f; \
307 float amp = 1.0f; \
308 float maxamp = 0.0f; \
309 float sum = 0.0f; \
310 for (int i = 0; i <= int(detail); i++) { \
311 float t = bp_perlin_signed(p * fscale); \
312 sum += t * amp; \
313 maxamp += amp; \
314 amp *= roughness; \
315 fscale *= lacunarity; \
316 } \
317 float rmd = detail - floor(detail); \
318 if (rmd != 0.0f) { \
319 float t2 = bp_perlin_signed(p * fscale); \
320 float sum2 = sum + t2 * amp; \
321 if (normalize) { \
322 return bp_pmix(0.5f * sum / maxamp + 0.5f, \
323 0.5f * sum2 / (maxamp + amp) + 0.5f, rmd); \
324 } \
325 return bp_pmix(sum, sum2, rmd); \
326 } \
327 if (normalize) { \
328 return 0.5f * sum / maxamp + 0.5f; \
329 } \
330 return sum; \
331} \
332 \
333BN_FN float bp_perlin_multi_fractal_##S(T p, float detail, float roughness, float lacunarity) { \
334 float value = 1.0f; \
335 float pwr = 1.0f; \
336 for (int i = 0; i <= int(detail); i++) { \
337 value *= (pwr * bp_perlin_signed(p) + 1.0f); \
338 pwr *= roughness; \
339 p = p * lacunarity; \
340 } \
341 float rmd = detail - floor(detail); \
342 if (rmd != 0.0f) { \
343 value *= (rmd * pwr * bp_perlin_signed(p) + 1.0f); \
344 } \
345 return value; \
346} \
347 \
348BN_FN float bp_perlin_hetero_terrain_##S(T p, float detail, float roughness, float lacunarity, \
349 float offset) { \
350 float pwr = roughness; \
351 /* First unscaled octave of function; later octaves are scaled. */ \
352 float value = offset + bp_perlin_signed(p); \
353 p = p * lacunarity; \
354 for (int i = 1; i <= int(detail); i++) { \
355 float increment = (bp_perlin_signed(p) + offset) * pwr * value; \
356 value += increment; \
357 pwr *= roughness; \
358 p = p * lacunarity; \
359 } \
360 float rmd = detail - floor(detail); \
361 if (rmd != 0.0f) { \
362 float increment2 = (bp_perlin_signed(p) + offset) * pwr * value; \
363 value += rmd * increment2; \
364 } \
365 return value; \
366} \
367 \
368BN_FN float bp_perlin_hybrid_multi_fractal_##S(T p, float detail, float roughness, \
369 float lacunarity, float offset, float gain) { \
370 float pwr = 1.0f; \
371 float value = 0.0f; \
372 float weight = 1.0f; \
373 for (int i = 0; (weight > 0.001f) && (i <= int(detail)); i++) { \
374 weight = min(weight, 1.0f); \
375 float signal = (bp_perlin_signed(p) + offset) * pwr; \
376 pwr *= roughness; \
377 value += weight * signal; \
378 weight *= gain * signal; \
379 p = p * lacunarity; \
380 } \
381 float rmd = detail - floor(detail); \
382 if ((rmd != 0.0f) && (weight > 0.001f)) { \
383 weight = min(weight, 1.0f); \
384 float signal2 = (bp_perlin_signed(p) + offset) * pwr; \
385 value += rmd * weight * signal2; \
386 } \
387 return value; \
388} \
389 \
390BN_FN float bp_perlin_ridged_multi_fractal_##S(T p, float detail, float roughness, \
391 float lacunarity, float offset, float gain) { \
392 float pwr = roughness; \
393 float signal = offset - abs(bp_perlin_signed(p)); \
394 signal *= signal; \
395 float value = signal; \
396 float weight = 1.0f; \
397 for (int i = 1; i <= int(detail); i++) { \
398 p = p * lacunarity; \
399 weight = clamp(signal * gain, 0.0f, 1.0f); \
400 signal = offset - abs(bp_perlin_signed(p)); \
401 signal *= signal; \
402 signal *= weight; \
403 value += signal * pwr; \
404 pwr *= roughness; \
405 } \
406 return value; \
407} \
408 \
409BN_FN float bp_perlin_select_##S(T p, float detail, float roughness, float lacunarity, \
410 float offset, float gain, int type, bool normalize) { \
411 if (type == BP_MULTIFRACTAL) { \
412 return bp_perlin_multi_fractal_##S(p, detail, roughness, lacunarity); \
413 } \
414 if (type == BP_FBM) { \
415 return bp_perlin_fbm_##S(p, detail, roughness, lacunarity, normalize); \
416 } \
417 if (type == BP_HYBRID_MULTIFRACTAL) { \
418 return bp_perlin_hybrid_multi_fractal_##S(p, detail, roughness, lacunarity, offset, gain); \
419 } \
420 if (type == BP_RIDGED_MULTIFRACTAL) { \
421 return bp_perlin_ridged_multi_fractal_##S(p, detail, roughness, lacunarity, offset, gain); \
422 } \
423 if (type == BP_HETERO_TERRAIN) { \
424 return bp_perlin_hetero_terrain_##S(p, detail, roughness, lacunarity, offset); \
425 } \
426 return 0.0f; \
427} \
428 \
429BN_FN float bp_perlin_fractal_distorted_##S(T position, float detail, float roughness, \
430 float lacunarity, float offset, float gain, float distortion, int type, bool normalize) { \
431 position = position + bp_perlin_distortion(position, distortion); \
432 return bp_perlin_select_##S(position, detail, roughness, lacunarity, offset, gain, type, \
433 normalize); \
434}
435
436BP_FRACTALS(float, 1)
437BP_FRACTALS(vec2, 2)
438BP_FRACTALS(vec3, 3)
439BP_FRACTALS(vec4, 4)
440
441// THE COLOUR SEEDS DIFFER PER DIMENSION: 1D uses 1 and 2, 2D uses 2 and 3, 3D
442// uses 3 and 4, 4D uses 4 and 5. Blender calls them "arbitrary seeds ... for
443// compatibility with shading functions" and there is no rule behind them --
444// which is why these four are written out rather than stamped from the macro.
445BN_FN vec3 bp_perlin_float3_fractal_distorted(float position, float detail, float roughness,
446 float lacunarity, float offset, float gain, float distortion, int type, bool normalize) {
447 position += bp_perlin_distortion(position, distortion);
448 return vec3(
449 bp_perlin_select_1(position, detail, roughness, lacunarity, offset, gain, type, normalize),
450 bp_perlin_select_1(position + bp_random_float_offset(1.0f), detail, roughness,
451 lacunarity, offset, gain, type, normalize),
452 bp_perlin_select_1(position + bp_random_float_offset(2.0f), detail, roughness,
453 lacunarity, offset, gain, type, normalize));
454}
455
456BN_FN vec3 bp_perlin_float3_fractal_distorted(vec2 position, float detail, float roughness,
457 float lacunarity, float offset, float gain, float distortion, int type, bool normalize) {
458 position = position + bp_perlin_distortion(position, distortion);
459 return vec3(
460 bp_perlin_select_2(position, detail, roughness, lacunarity, offset, gain, type, normalize),
461 bp_perlin_select_2(position + bp_random_float2_offset(2.0f), detail, roughness,
462 lacunarity, offset, gain, type, normalize),
463 bp_perlin_select_2(position + bp_random_float2_offset(3.0f), detail, roughness,
464 lacunarity, offset, gain, type, normalize));
465}
466
467BN_FN vec3 bp_perlin_float3_fractal_distorted(vec3 position, float detail, float roughness,
468 float lacunarity, float offset, float gain, float distortion, int type, bool normalize) {
469 position = position + bp_perlin_distortion(position, distortion);
470 return vec3(
471 bp_perlin_select_3(position, detail, roughness, lacunarity, offset, gain, type, normalize),
472 bp_perlin_select_3(position + bp_random_float3_offset(3.0f), detail, roughness,
473 lacunarity, offset, gain, type, normalize),
474 bp_perlin_select_3(position + bp_random_float3_offset(4.0f), detail, roughness,
475 lacunarity, offset, gain, type, normalize));
476}
477
478BN_FN vec3 bp_perlin_float3_fractal_distorted(vec4 position, float detail, float roughness,
479 float lacunarity, float offset, float gain, float distortion, int type, bool normalize) {
480 position = position + bp_perlin_distortion(position, distortion);
481 return vec3(
482 bp_perlin_select_4(position, detail, roughness, lacunarity, offset, gain, type, normalize),
483 bp_perlin_select_4(position + bp_random_float4_offset(4.0f), detail, roughness,
484 lacunarity, offset, gain, type, normalize),
485 bp_perlin_select_4(position + bp_random_float4_offset(5.0f), detail, roughness,
486 lacunarity, offset, gain, type, normalize));
487}
488
489#endif // PN_BLENDER_PERLIN
tests/test_hash_parity.cpp +0 -584
……@@ -1,584 +0,0 @@
1// ============================================================================
2// HASH PARITY — is the GPU's integer hash the SAME FUNCTION as the CPU's?
3//
4// WHY THIS IS THE FIRST GATE OF THE NOISE WORK.
5//
6// Everything above the hash inherits it. Voronoi's cell offsets are this hash;
7// Perlin's lattice gradients are this hash; Blender's White Noise Texture node
8// is this hash and nothing else. Get it wrong and nothing above it can ever
9// match, and the failure looks like the STRUCTURE rather than the hash --
10// which is exactly how ProcNodes_Hash2D cost this repo a terrain chain tuned
11// against a correlation of -0.0043 that could not converge.
12//
13// It is also the one layer where "agree" can mean BIT-EXACT. The op-parity
14// gate next door compares floats at 1e-5 because libm and a GPU part company
15// on sin/pow. This is uint32 arithmetic: GLSL requires unsigned overflow to
16// wrap and C++ defines it as modulo 2^32, so the two languages compute the
17// same integers or they do not. There is no tolerance to hide in.
18//
19// THERE IS ONLY ONE IMPLEMENTATION, AND THIS COMPILES IT TWICE.
20// CPU src/shared/blender_hash.inc through blender_noise_cpp_shim.h
21// GPU the SAME .inc, extracted from the generated header the engine
22// hands the driver
23//
24// That is a different question from the one this gate asked on 2026-09-05,
25// when the subject was GpuCompiler::get_noise_library_glsl()'s independently
26// hand-written Jenkins hash and the answer was 22/22 -- two implementations
27// that happened to agree. What it can still catch is the part that survives
28// having one text: the places GLSL and C++ read the SAME characters
29// differently. Arithmetic vs logical shift, signed overflow, the bit cast,
30// and the literal hazard -- in GLSL `1.0` is a float and in C++ it is a
31// double, so a reciprocal computed from one can round to a different float.
32//
33// Build (no engine, no godot-cpp, no scons):
34// g++ -O2 -std=c++17 -I tests/stub_include -I src/shared \
35// -o /tmp/hash_parity tests/test_hash_parity.cpp -lvulkan
36//
37// Run:
38// /tmp/hash_parity # default subject, see SUBJECT below
39// /tmp/hash_parity <file> <marker> # extract a raw string from <file>
40//
41// Exit status is 0 when every case agrees exactly.
42// ============================================================================
43
44#include <vulkan/vulkan.h>
45
46#include <cstdint>
47#include <cstdio>
48#include <cstdlib>
49#include <cstring>
50#include <fstream>
51#include <sstream>
52#include <string>
53#include <vector>
54
55#include "blender_noise_cpp_shim.h"
56
57// The subject: the GENERATED header, because that is the text the engine
58// hands the driver. Pointing at src/shared/blender_hash.inc instead would
59// compare the source and skip the generator, and a stale .gen.h is exactly
60// the failure the generate-don't-check-in rule exists to make impossible.
61// Either spelling works -- a path ending in .inc is taken whole, anything
62// else has its raw string extracted.
63static const char *SUBJECT_FILE = "src/gpu/blender_hash_glsl.gen.h";
64static const char *SUBJECT_MARK = "BLENDER_HASH_GLSL";
65
66static void die(const char *what, VkResult r) {
67 fprintf(stderr, "FATAL: %s -> VkResult %d\n", what, (int)r);
68 exit(1);
69}
70#define VK(x) do { VkResult _r = (x); if (_r != VK_SUCCESS) die(#x, _r); } while (0)
71
72static std::string read_file(const std::string &path) {
73 std::ifstream f(path);
74 if (!f) { fprintf(stderr, "FATAL: cannot read %s\n", path.c_str()); exit(1); }
75 std::stringstream ss;
76 ss << f.rdbuf();
77 return ss.str();
78}
79
80// Pull the GLSL out of a C++ raw string literal that follows `marker`. The
81// point is that the gate compiles the SAME BYTES the engine hands the driver;
82// re-typing them here would put the gate's blind spot exactly on the change.
83static std::string extract_raw_string(const std::string &src, const std::string &marker) {
84 size_t m = src.find(marker);
85 if (m == std::string::npos) {
86 fprintf(stderr, "FATAL: marker '%s' not found\n", marker.c_str());
87 exit(1);
88 }
89 size_t rq = src.find("R\"", m);
90 if (rq == std::string::npos) {
91 fprintf(stderr, "FATAL: no raw string after '%s'\n", marker.c_str());
92 exit(1);
93 }
94 // A raw string may carry a delimiter -- R"DELIM( ... )DELIM" -- and the
95 // generated headers do, because their payload is GLSL full of parentheses.
96 // Reading the delimiter off the text rather than assuming R"( is the
97 // difference between this gate running and this gate looking like the
98 // subject is missing.
99 size_t lp = src.find('(', rq);
100 if (lp == std::string::npos) {
101 fprintf(stderr, "FATAL: malformed raw string after '%s'\n", marker.c_str());
102 exit(1);
103 }
104 const std::string delim = src.substr(rq + 2, lp - (rq + 2));
105 const std::string term = ")" + delim + "\"";
106 size_t close = src.find(term, lp + 1);
107 if (close == std::string::npos) {
108 fprintf(stderr, "FATAL: unterminated raw string (delimiter '%s') after '%s'\n",
109 delim.c_str(), marker.c_str());
110 exit(1);
111 }
112 return src.substr(lp + 1, close - (lp + 1));
113}
114
115static std::vector<uint32_t> compile_glsl(const std::string &src) {
116 char in_path[] = "/tmp/proc_hash_parity_XXXXXX";
117 int fd = mkstemp(in_path);
118 if (fd < 0) { perror("mkstemp"); exit(1); }
119 FILE *f = fdopen(fd, "w");
120 fwrite(src.data(), 1, src.size(), f);
121 fclose(f);
122 std::string spv = std::string(in_path) + ".spv";
123 std::string cmd = "glslc -fshader-stage=comp -o " + spv + " " + in_path;
124 if (system(cmd.c_str()) != 0) {
125 fprintf(stderr, "FATAL: glslc failed (source left at %s)\n", in_path);
126 exit(1);
127 }
128 FILE *sf = fopen(spv.c_str(), "rb");
129 fseek(sf, 0, SEEK_END); long n = ftell(sf); fseek(sf, 0, SEEK_SET);
130 std::vector<uint32_t> out(n / 4);
131 if (fread(out.data(), 1, n, sf) != (size_t)n) { fprintf(stderr, "FATAL: short read\n"); exit(1); }
132 fclose(sf);
133 remove(in_path); remove(spv.c_str());
134 return out;
135}
136
137struct Ctx {
138 VkInstance instance = VK_NULL_HANDLE;
139 VkPhysicalDevice phys = VK_NULL_HANDLE;
140 VkDevice dev = VK_NULL_HANDLE;
141 VkQueue queue = VK_NULL_HANDLE;
142 uint32_t qfam = 0;
143 VkCommandPool cmdpool = VK_NULL_HANDLE;
144 VkPhysicalDeviceMemoryProperties memprops{};
145 char devname[256] = {0};
146};
147
148static uint32_t find_mem(Ctx &c, uint32_t bits, VkMemoryPropertyFlags want) {
149 for (uint32_t i = 0; i < c.memprops.memoryTypeCount; i++) {
150 if ((bits & (1u << i)) && (c.memprops.memoryTypes[i].propertyFlags & want) == want) {
151 return i;
152 }
153 }
154 fprintf(stderr, "FATAL: no memory type with flags 0x%x\n", want);
155 exit(1);
156}
157
158static void init_vulkan(Ctx &c) {
159 VkApplicationInfo app{VK_STRUCTURE_TYPE_APPLICATION_INFO};
160 app.pApplicationName = "proc-nodes hash parity";
161 app.apiVersion = VK_API_VERSION_1_1;
162 VkInstanceCreateInfo ii{VK_STRUCTURE_TYPE_INSTANCE_CREATE_INFO};
163 ii.pApplicationInfo = &app;
164 VK(vkCreateInstance(&ii, nullptr, &c.instance));
165
166 uint32_t n = 0;
167 VK(vkEnumeratePhysicalDevices(c.instance, &n, nullptr));
168 if (!n) { fprintf(stderr, "FATAL: no Vulkan device\n"); exit(1); }
169 std::vector<VkPhysicalDevice> devs(n);
170 VK(vkEnumeratePhysicalDevices(c.instance, &n, devs.data()));
171 c.phys = devs[0];
172 for (auto d : devs) {
173 VkPhysicalDeviceProperties p;
174 vkGetPhysicalDeviceProperties(d, &p);
175 if (p.deviceType == VK_PHYSICAL_DEVICE_TYPE_DISCRETE_GPU) { c.phys = d; break; }
176 }
177 VkPhysicalDeviceProperties props;
178 vkGetPhysicalDeviceProperties(c.phys, &props);
179 snprintf(c.devname, sizeof(c.devname), "%s", props.deviceName);
180 vkGetPhysicalDeviceMemoryProperties(c.phys, &c.memprops);
181
182 uint32_t qn = 0;
183 vkGetPhysicalDeviceQueueFamilyProperties(c.phys, &qn, nullptr);
184 std::vector<VkQueueFamilyProperties> qs(qn);
185 vkGetPhysicalDeviceQueueFamilyProperties(c.phys, &qn, qs.data());
186 bool found = false;
187 for (uint32_t i = 0; i < qn; i++) {
188 if (qs[i].queueFlags & VK_QUEUE_COMPUTE_BIT) { c.qfam = i; found = true; break; }
189 }
190 if (!found) { fprintf(stderr, "FATAL: no compute queue\n"); exit(1); }
191
192 float prio = 1.0f;
193 VkDeviceQueueCreateInfo qi{VK_STRUCTURE_TYPE_DEVICE_QUEUE_CREATE_INFO};
194 qi.queueFamilyIndex = c.qfam; qi.queueCount = 1; qi.pQueuePriorities = &prio;
195 VkDeviceCreateInfo di{VK_STRUCTURE_TYPE_DEVICE_CREATE_INFO};
196 di.queueCreateInfoCount = 1; di.pQueueCreateInfos = &qi;
197 VK(vkCreateDevice(c.phys, &di, nullptr, &c.dev));
198 vkGetDeviceQueue(c.dev, c.qfam, 0, &c.queue);
199
200 VkCommandPoolCreateInfo ci{VK_STRUCTURE_TYPE_COMMAND_POOL_CREATE_INFO};
201 ci.queueFamilyIndex = c.qfam;
202 ci.flags = VK_COMMAND_POOL_CREATE_RESET_COMMAND_BUFFER_BIT;
203 VK(vkCreateCommandPool(c.dev, &ci, nullptr, &c.cmdpool));
204}
205
206// --- the cases ---
207//
208// Every family, and the inputs are chosen where an integer hash actually
209// breaks: zero, the all-ones word a shift can eat, single-bit words that
210// expose a rotate written as a shift, the two's-complement spelling of a
211// negative lattice coordinate (an ORDINARY input -- Blender indexes its
212// lattice with signed ints cast to uint), and real float bit patterns, since
213// hash_float feeds exactly those.
214//
215// FLOAT INPUTS CROSS AS BIT PATTERNS, never as decimal literals. Writing
216// 3.14159265 into the shader would ask glslc to round a decimal string and
217// C++ to round the same string, and nothing guarantees they land on the same
218// float -- the gate would then be measuring the two parsers. uintBitsToFloat
219// of an exact word cannot be rounded by anyone.
220//
221// FLOAT RESULTS COME BACK AS BIT PATTERNS TOO, so the comparison stays
222// exact for them as well. That is a strong claim to make of a GPU, and it is
223// the right one here: every float this layer produces comes from an integer
224// conversion, a division and a multiply, all correctly rounded by IEEE 754 on
225// both sides. A near-miss would be a real finding, not noise to be tolerated,
226// so there is deliberately no epsilon anywhere in this file.
227
228struct Case {
229 std::string name;
230 std::string glsl; // an expression of type uint
231 uint32_t cpu; // the CPU's answer, as bits
232};
233
234static uint32_t bits_of(float f) {
235 uint32_t u;
236 memcpy(&u, &f, sizeof(u));
237 return u;
238}
239static float float_of(uint32_t u) {
240 float f;
241 memcpy(&f, &u, sizeof(f));
242 return f;
243}
244
245// A float as a GLSL expression that cannot be misparsed.
246static std::string F(float v) {
247 char b[64];
248 snprintf(b, sizeof(b), "uintBitsToFloat(0x%08xu)", bits_of(v));
249 return b;
250}
251static std::string U(uint32_t v) {
252 char b[32];
253 snprintf(b, sizeof(b), "%uu", v);
254 return b;
255}
256static std::string I(int32_t v) {
257 char b[32];
258 // GLSL has no negative literal; it is a unary minus on a positive one,
259 // and -2147483648 has no positive spelling that fits an int.
260 if (v == INT32_MIN) return "(-2147483647 - 1)";
261 snprintf(b, sizeof(b), "%d", v);
262 return b;
263}
264
265int main(int argc, char **argv) {
266 using namespace godot::bnoisegl;
267
268 const std::string file = (argc > 1) ? argv[1] : SUBJECT_FILE;
269 const std::string mark = (argc > 2) ? argv[2] : SUBJECT_MARK;
270
271 std::string src_text = read_file(file);
272 const bool raw_inc = file.size() > 4 && file.compare(file.size() - 4, 4, ".inc") == 0;
273 std::string lib = raw_inc ? src_text : extract_raw_string(src_text, mark);
274 if (lib.find("bn_hash_bit_final") == std::string::npos) {
275 fprintf(stderr, "FATAL: extracted text has no bn_hash_bit_final -- wrong subject?\n");
276 return 1;
277 }
278
279 const uint32_t KU[] = {
280 0u, 1u, 2u, 7u, 16u, 255u,
281 0x0000FFFFu, 0xFFFF0000u, 0x80000000u, 0xFFFFFFFFu,
282 0xdeadbeefu, 0xcafebabeu, 0x12345678u,
283 (uint32_t)-1, (uint32_t)-7, (uint32_t)-1000000
284 };
285 const int NU = (int)(sizeof(KU) / sizeof(KU[0]));
286
287 const float KF[] = {
288 0.0f, -0.0f, 1.0f, -1.0f, 0.5f, -0.25f, 2.5f, 7.0f,
289 3.14159265f, -7.25f, 1e7f, -1e7f, 1e-7f, 123456.789f, -0.001f, 65536.0f
290 };
291 const int NF = (int)(sizeof(KF) / sizeof(KF[0]));
292
293 std::vector<Case> cases;
294 auto add = [&](const std::string &name, const std::string &glsl, uint32_t cpu) {
295 cases.push_back(Case{name, glsl, cpu});
296 };
297
298 // -- Jenkins, the four arities. Integer in, integer out.
299 for (int i = 0; i < NU; i++) {
300 uint32_t a = KU[i], b = KU[(i + 3) % NU], c = KU[(i + 7) % NU], d = KU[(i + 11) % NU];
301 add("bn_hash1", "bn_hash(" + U(a) + ")", bn_hash(a));
302 add("bn_hash2", "bn_hash(" + U(a) + "," + U(b) + ")", bn_hash(a, b));
303 add("bn_hash3", "bn_hash(" + U(a) + "," + U(b) + "," + U(c) + ")", bn_hash(a, b, c));
304 add("bn_hash4", "bn_hash(" + U(a) + "," + U(b) + "," + U(c) + "," + U(d) + ")",
305 bn_hash(a, b, c, d));
306 add("bn_uint_to_float_01", "floatBitsToUint(bn_uint_to_float_01(" + U(a) + "))",
307 bits_of(bn_uint_to_float_01(a)));
308 add("bn_hash_to_float1", "floatBitsToUint(bn_hash_to_float(" + U(a) + "))",
309 bits_of(bn_hash_to_float(a)));
310 add("bn_hash_to_float2", "floatBitsToUint(bn_hash_to_float(" + U(a) + "," + U(b) + "))",
311 bits_of(bn_hash_to_float(a, b)));
312 add("bn_hash_to_float3",
313 "floatBitsToUint(bn_hash_to_float(" + U(a) + "," + U(b) + "," + U(c) + "))",
314 bits_of(bn_hash_to_float(a, b, c)));
315 add("bn_hash_to_float4",
316 "floatBitsToUint(bn_hash_to_float(" + U(a) + "," + U(b) + "," + U(c) + "," + U(d) + "))",
317 bits_of(bn_hash_to_float(a, b, c, d)));
318 }
319
320 // -- the float-keyed families. The bit cast is the interesting part: C++
321 // needs memcpy where GLSL has floatBitsToUint, so this is the one place
322 // the two texts are doing visibly different things underneath.
323 for (int i = 0; i < NF; i++) {
324 float x = KF[i], y = KF[(i + 3) % NF], z = KF[(i + 5) % NF], w = KF[(i + 9) % NF];
325 const std::string sx = F(x), sy = F(y), sz = F(z), sw = F(w);
326 const std::string v2 = "vec2(" + sx + "," + sy + ")";
327 const std::string v3 = "vec3(" + sx + "," + sy + "," + sz + ")";
328 const std::string v4 = "vec4(" + sx + "," + sy + "," + sz + "," + sw + ")";
329 const vec2 c2(x, y);
330 const vec3 c3(x, y, z);
331 const vec4 c4(x, y, z, w);
332
333 add("bn_hash_float(f)", "bn_hash_float(" + sx + ")", bn_hash_float(x));
334 add("bn_hash_float(v2)", "bn_hash_float(" + v2 + ")", bn_hash_float(c2));
335 add("bn_hash_float(v3)", "bn_hash_float(" + v3 + ")", bn_hash_float(c3));
336 add("bn_hash_float(v4)", "bn_hash_float(" + v4 + ")", bn_hash_float(c4));
337
338 add("bn_hff(f)", "floatBitsToUint(bn_hash_float_to_float(" + sx + "))", bits_of(bn_hash_float_to_float(x)));
339 add("bn_hff(v2)", "floatBitsToUint(bn_hash_float_to_float(" + v2 + "))", bits_of(bn_hash_float_to_float(c2)));
340 add("bn_hff(v3)", "floatBitsToUint(bn_hash_float_to_float(" + v3 + "))", bits_of(bn_hash_float_to_float(c3)));
341 add("bn_hff(v4)", "floatBitsToUint(bn_hash_float_to_float(" + v4 + "))", bits_of(bn_hash_float_to_float(c4)));
342
343 const char *comp[4] = {"x", "y", "z", "w"};
344 {
345 vec2 r = bn_hash_float_to_float2(c2);
346 const float rv[2] = {r.x, r.y};
347 for (int k = 0; k < 2; k++)
348 add("bn_hff2(v2)", "floatBitsToUint(bn_hash_float_to_float2(" + v2 + ")." + comp[k] + ")", bits_of(rv[k]));
349 }
350 {
351 vec2 r = bn_hash_float_to_float2(c3);
352 const float rv[2] = {r.x, r.y};
353 for (int k = 0; k < 2; k++)
354 add("bn_hff2(v3)", "floatBitsToUint(bn_hash_float_to_float2(" + v3 + ")." + comp[k] + ")", bits_of(rv[k]));
355 }
356 {
357 vec2 r = bn_hash_float_to_float2(c4);
358 const float rv[2] = {r.x, r.y};
359 for (int k = 0; k < 2; k++)
360 add("bn_hff2(v4)", "floatBitsToUint(bn_hash_float_to_float2(" + v4 + ")." + comp[k] + ")", bits_of(rv[k]));
361 }
362 {
363 vec3 r = bn_hash_float_to_float3(x);
364 const float rv[3] = {r.x, r.y, r.z};
365 for (int k = 0; k < 3; k++)
366 add("bn_hff3(f)", "floatBitsToUint(bn_hash_float_to_float3(" + sx + ")." + comp[k] + ")", bits_of(rv[k]));
367 }
368 {
369 vec3 r = bn_hash_float_to_float3(c2);
370 const float rv[3] = {r.x, r.y, r.z};
371 for (int k = 0; k < 3; k++)
372 add("bn_hff3(v2)", "floatBitsToUint(bn_hash_float_to_float3(" + v2 + ")." + comp[k] + ")", bits_of(rv[k]));
373 }
374 {
375 vec3 r = bn_hash_float_to_float3(c3);
376 const float rv[3] = {r.x, r.y, r.z};
377 for (int k = 0; k < 3; k++)
378 add("bn_hff3(v3)", "floatBitsToUint(bn_hash_float_to_float3(" + v3 + ")." + comp[k] + ")", bits_of(rv[k]));
379 }
380 {
381 vec3 r = bn_hash_float_to_float3(c4);
382 const float rv[3] = {r.x, r.y, r.z};
383 for (int k = 0; k < 3; k++)
384 add("bn_hff3(v4)", "floatBitsToUint(bn_hash_float_to_float3(" + v4 + ")." + comp[k] + ")", bits_of(rv[k]));
385 }
386 {
387 vec4 r = bn_hash_float_to_float4(c4);
388 const float rv[4] = {r.x, r.y, r.z, r.w};
389 for (int k = 0; k < 4; k++)
390 add("bn_hff4(v4)", "floatBitsToUint(bn_hash_float_to_float4(" + v4 + ")." + comp[k] + ")", bits_of(rv[k]));
391 }
392 }
393
394 // -- PCG. Signed throughout, so the shift is ARITHMETIC and the wrap goes
395 // through uint on both sides. This is where a language difference would
396 // actually bite: neither C++ nor GLSL defines signed overflow.
397 for (int i = 0; i < NU; i++) {
398 int32_t a = (int32_t)KU[i], b = (int32_t)KU[(i + 3) % NU];
399 int32_t c = (int32_t)KU[(i + 7) % NU], d = (int32_t)KU[(i + 11) % NU];
400 const std::string i2 = "ivec2(" + I(a) + "," + I(b) + ")";
401 const std::string i3 = "ivec3(" + I(a) + "," + I(b) + "," + I(c) + ")";
402 const std::string i4 = "ivec4(" + I(a) + "," + I(b) + "," + I(c) + "," + I(d) + ")";
403 const ivec2 c2(a, b);
404 const ivec3 c3(a, b, c);
405 const ivec4 c4(a, b, c, d);
406 const char *comp[4] = {"x", "y", "z", "w"};
407
408 add("bn_wrap_mul", "uint(bn_wrap_mul(" + I(a) + "," + I(b) + "))", (uint32_t)bn_wrap_mul(a, b));
409 add("bn_wrap_add", "uint(bn_wrap_add(" + I(a) + "," + I(b) + "))", (uint32_t)bn_wrap_add(a, b));
410 add("bn_int_to_float_01", "floatBitsToUint(bn_int_to_float_01(" + I(a) + "))",
411 bits_of(bn_int_to_float_01(a)));
412
413 {
414 ivec2 r = bn_hash_pcg2d_i(c2);
415 const int32_t rv[2] = {r.x, r.y};
416 for (int k = 0; k < 2; k++)
417 add("bn_hash_pcg2d_i", "uint(bn_hash_pcg2d_i(" + i2 + ")." + comp[k] + ")", (uint32_t)rv[k]);
418 }
419 {
420 ivec3 r = bn_hash_pcg3d_i(c3);
421 const int32_t rv[3] = {r.x, r.y, r.z};
422 for (int k = 0; k < 3; k++)
423 add("bn_hash_pcg3d_i", "uint(bn_hash_pcg3d_i(" + i3 + ")." + comp[k] + ")", (uint32_t)rv[k]);
424 }
425 {
426 ivec4 r = bn_hash_pcg4d_i(c4);
427 const int32_t rv[4] = {r.x, r.y, r.z, r.w};
428 for (int k = 0; k < 4; k++)
429 add("bn_hash_pcg4d_i", "uint(bn_hash_pcg4d_i(" + i4 + ")." + comp[k] + ")", (uint32_t)rv[k]);
430 }
431 {
432 vec2 r = bn_hash_int_to_float2(c2);
433 const float rv[2] = {r.x, r.y};
434 for (int k = 0; k < 2; k++)
435 add("bn_hash_int_to_float2", "floatBitsToUint(bn_hash_int_to_float2(" + i2 + ")." + comp[k] + ")", bits_of(rv[k]));
436 }
437 {
438 vec3 r = bn_hash_int_to_float3(c3);
439 const float rv[3] = {r.x, r.y, r.z};
440 for (int k = 0; k < 3; k++)
441 add("bn_hash_int_to_float3(i3)", "floatBitsToUint(bn_hash_int_to_float3(" + i3 + ")." + comp[k] + ")", bits_of(rv[k]));
442 }
443 {
444 vec4 r = bn_hash_int_to_float4(c4);
445 const float rv[4] = {r.x, r.y, r.z, r.w};
446 for (int k = 0; k < 4; k++)
447 add("bn_hash_int_to_float4", "floatBitsToUint(bn_hash_int_to_float4(" + i4 + ")." + comp[k] + ")", bits_of(rv[k]));
448 }
449 {
450 vec3 r = bn_hash_int_to_float3(c2);
451 const float rv[3] = {r.x, r.y, r.z};
452 for (int k = 0; k < 3; k++)
453 add("bn_hash_int_to_float3(i2)", "floatBitsToUint(bn_hash_int_to_float3(" + i2 + ")." + comp[k] + ")", bits_of(rv[k]));
454 }
455 {
456 vec3 r = bn_hash_int_to_float3(c4);
457 const float rv[3] = {r.x, r.y, r.z};
458 for (int k = 0; k < 3; k++)
459 add("bn_hash_int_to_float3(i4)", "floatBitsToUint(bn_hash_int_to_float3(" + i4 + ")." + comp[k] + ")", bits_of(rv[k]));
460 }
461 }
462
463 // One shader: the subject's own text, then one line per case. Baking the
464 // inputs in keeps this to a single buffer -- there is nothing to get wrong
465 // in an upload that could be mistaken for the hash disagreeing.
466 std::string calls;
467 for (size_t i = 0; i < cases.size(); i++) {
468 char c[64];
469 snprintf(c, sizeof(c), " out_v.v[%zu] = ", i);
470 calls += std::string(c) + cases[i].glsl + ";\n";
471 }
472 std::string shader =
473 "#version 450\n"
474 "layout(local_size_x = 1) in;\n"
475 "layout(std430, binding = 0) buffer Out { uint v[]; } out_v;\n" +
476 lib +
477 "\nvoid main() {\n" + calls + "}\n";
478
479 Ctx c{};
480 init_vulkan(c);
481 printf("device : %s\n", c.devname);
482 printf("subject: %s%s (%zu bytes of GLSL, extracted not retyped)\n",
483 file.c_str(), raw_inc ? "" : (" :: " + mark).c_str(), lib.size());
484 printf("cases : %zu, compared EXACTLY (bit patterns, no epsilon)\n\n", cases.size());
485
486 std::vector<uint32_t> spv = compile_glsl(shader);
487
488 VkBuffer buf;
489 VkDeviceMemory mem;
490 void *mapped = nullptr;
491 const VkDeviceSize bytes = sizeof(uint32_t) * cases.size();
492 {
493 VkBufferCreateInfo bi{VK_STRUCTURE_TYPE_BUFFER_CREATE_INFO};
494 bi.size = bytes;
495 bi.usage = VK_BUFFER_USAGE_STORAGE_BUFFER_BIT;
496 VK(vkCreateBuffer(c.dev, &bi, nullptr, &buf));
497 VkMemoryRequirements mr;
498 vkGetBufferMemoryRequirements(c.dev, buf, &mr);
499 VkMemoryAllocateInfo ai{VK_STRUCTURE_TYPE_MEMORY_ALLOCATE_INFO};
500 ai.allocationSize = mr.size;
501 ai.memoryTypeIndex = find_mem(c, mr.memoryTypeBits,
502 VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT | VK_MEMORY_PROPERTY_HOST_COHERENT_BIT);
503 VK(vkAllocateMemory(c.dev, &ai, nullptr, &mem));
504 VK(vkBindBufferMemory(c.dev, buf, mem, 0));
505 VK(vkMapMemory(c.dev, mem, 0, VK_WHOLE_SIZE, 0, &mapped));
506 memset(mapped, 0, bytes);
507 }
508
509 VkDescriptorSetLayoutBinding bind{};
510 bind.binding = 0;
511 bind.descriptorType = VK_DESCRIPTOR_TYPE_STORAGE_BUFFER;
512 bind.descriptorCount = 1;
513 bind.stageFlags = VK_SHADER_STAGE_COMPUTE_BIT;
514 VkDescriptorSetLayoutCreateInfo dli{VK_STRUCTURE_TYPE_DESCRIPTOR_SET_LAYOUT_CREATE_INFO};
515 dli.bindingCount = 1; dli.pBindings = &bind;
516 VkDescriptorSetLayout dsl;
517 VK(vkCreateDescriptorSetLayout(c.dev, &dli, nullptr, &dsl));
518
519 VkPipelineLayoutCreateInfo pli{VK_STRUCTURE_TYPE_PIPELINE_LAYOUT_CREATE_INFO};
520 pli.setLayoutCount = 1; pli.pSetLayouts = &dsl;
521 VkPipelineLayout playout;
522 VK(vkCreatePipelineLayout(c.dev, &pli, nullptr, &playout));
523
524 VkShaderModuleCreateInfo smi{VK_STRUCTURE_TYPE_SHADER_MODULE_CREATE_INFO};
525 smi.codeSize = spv.size() * 4; smi.pCode = spv.data();
526 VkShaderModule mod;
527 VK(vkCreateShaderModule(c.dev, &smi, nullptr, &mod));
528
529 VkPipelineShaderStageCreateInfo st{VK_STRUCTURE_TYPE_PIPELINE_SHADER_STAGE_CREATE_INFO};
530 st.stage = VK_SHADER_STAGE_COMPUTE_BIT; st.module = mod; st.pName = "main";
531 VkComputePipelineCreateInfo cpi{VK_STRUCTURE_TYPE_COMPUTE_PIPELINE_CREATE_INFO};
532 cpi.stage = st; cpi.layout = playout;
533 VkPipeline pipe;
534 VK(vkCreateComputePipelines(c.dev, VK_NULL_HANDLE, 1, &cpi, nullptr, &pipe));
535
536 VkDescriptorPoolSize ps{VK_DESCRIPTOR_TYPE_STORAGE_BUFFER, 1};
537 VkDescriptorPoolCreateInfo dpi{VK_STRUCTURE_TYPE_DESCRIPTOR_POOL_CREATE_INFO};
538 dpi.maxSets = 1; dpi.poolSizeCount = 1; dpi.pPoolSizes = &ps;
539 VkDescriptorPool dpool;
540 VK(vkCreateDescriptorPool(c.dev, &dpi, nullptr, &dpool));
541 VkDescriptorSetAllocateInfo dai{VK_STRUCTURE_TYPE_DESCRIPTOR_SET_ALLOCATE_INFO};
542 dai.descriptorPool = dpool; dai.descriptorSetCount = 1; dai.pSetLayouts = &dsl;
543 VkDescriptorSet dset;
544 VK(vkAllocateDescriptorSets(c.dev, &dai, &dset));
545 VkDescriptorBufferInfo dbi{buf, 0, VK_WHOLE_SIZE};
546 VkWriteDescriptorSet w{VK_STRUCTURE_TYPE_WRITE_DESCRIPTOR_SET};
547 w.dstSet = dset; w.dstBinding = 0; w.descriptorCount = 1;
548 w.descriptorType = VK_DESCRIPTOR_TYPE_STORAGE_BUFFER; w.pBufferInfo = &dbi;
549 vkUpdateDescriptorSets(c.dev, 1, &w, 0, nullptr);
550
551 VkCommandBufferAllocateInfo cbi{VK_STRUCTURE_TYPE_COMMAND_BUFFER_ALLOCATE_INFO};
552 cbi.commandPool = c.cmdpool; cbi.level = VK_COMMAND_BUFFER_LEVEL_PRIMARY; cbi.commandBufferCount = 1;
553 VkCommandBuffer cmd;
554 VK(vkAllocateCommandBuffers(c.dev, &cbi, &cmd));
555 VkCommandBufferBeginInfo bbi{VK_STRUCTURE_TYPE_COMMAND_BUFFER_BEGIN_INFO};
556 bbi.flags = VK_COMMAND_BUFFER_USAGE_ONE_TIME_SUBMIT_BIT;
557 VK(vkBeginCommandBuffer(cmd, &bbi));
558 vkCmdBindPipeline(cmd, VK_PIPELINE_BIND_POINT_COMPUTE, pipe);
559 vkCmdBindDescriptorSets(cmd, VK_PIPELINE_BIND_POINT_COMPUTE, playout, 0, 1, &dset, 0, nullptr);
560 vkCmdDispatch(cmd, 1, 1, 1);
561 VK(vkEndCommandBuffer(cmd));
562 VkSubmitInfo si{VK_STRUCTURE_TYPE_SUBMIT_INFO};
563 si.commandBufferCount = 1; si.pCommandBuffers = &cmd;
564 VK(vkQueueSubmit(c.queue, 1, &si, VK_NULL_HANDLE));
565 VK(vkQueueWaitIdle(c.queue));
566
567
568 const uint32_t *got = (const uint32_t *)mapped;
569 int bad = 0;
570 std::string worst;
571 for (size_t i = 0; i < cases.size(); i++) {
572 if (got[i] != cases[i].cpu) {
573 bad++;
574 if (bad <= 20) {
575 printf(" FAIL %-28s cpu=0x%08x (%.9g) gpu=0x%08x (%.9g)\n",
576 cases[i].name.c_str(), cases[i].cpu, float_of(cases[i].cpu),
577 got[i], float_of(got[i]));
578 printf(" %s\n", cases[i].glsl.c_str());
579 }
580 }
581 }
582 printf("\n%zu/%zu cases agree exactly\n", cases.size() - (size_t)bad, cases.size());
583 return bad ? 1 : 0;
584}
tests/test_shared_noise_parity.cpp +979 -0
……@@ -0,0 +1,979 @@
1// ============================================================================
2// SHARED-NOISE PARITY — is the .inc the same function on both sides?
3//
4// src/shared/blender_hash.inc and src/shared/blender_perlin.inc are compiled
5// TWICE: as C++ through blender_noise_cpp_shim.h, and as GLSL through the
6// generated headers the engine hands the driver. There is only one
7// implementation of each algorithm, which is the entire point -- but "one
8// text" is not "one function" until something compiles both and compares.
9// What survives having one text is the set of places the two LANGUAGES read
10// the same characters differently: arithmetic vs logical shift, signed
11// overflow, the bit cast, truncated vs floored modulo, and float literals
12// that are double in C++.
13//
14// NEITHER SIDE IS WRITTEN FOR THE TEST. The CPU calls the shim; the GLSL is
15// EXTRACTED from the generated header at run time, never retyped. The erosion
16// filter's lesson was that a transcription used AS the reference can only ever
17// agree with itself.
18//
19// TWO CLASSES OF CASE, AND THEY ARE NOT COMPARED THE SAME WAY.
20//
21// EXACT the hash layer. Integer arithmetic, which GLSL requires to wrap and
22// C++ defines as modulo 2^32, so the two languages compute the same
23// integers or they do not. Its float results come from an integer
24// conversion, a division and a multiply, all correctly rounded by
25// IEEE 754 on both sides. No epsilon, and a near-miss is a finding.
26//
27// NEAR the perlin layer. Up to eight octaves of multiply-add, where a GPU
28// may contract a*b+c into one FMA and the CPU rounds twice. That is a
29// difference in the last bits, not in the function, so these carry a
30// relative tolerance -- and the run PRINTS the worst error it saw and
31// how many were bit-identical anyway, because a tolerance nobody
32// measures against is where a real divergence hides.
33//
34// ---------------------------------------------------------------------------
35// ⚠ WHY THIS IS DATA-DRIVEN, AND WHY THE MEMORY GUARD EXISTS.
36//
37// The first version of this gate did what tests/test_emitted_op_parity.cpp
38// does: emit one line of GLSL per case into a single main(), on the reasoning
39// that "246 pipelines to answer 246 questions is a slow gate nobody runs".
40// That is sound for 658 cheap Math cases. It does not survive 1300 perlin
41// cases, each an eight-octave chain: the shader becomes a combinatorial
42// blow-up that is inlined and register-allocated IN THIS PROCESS, and on
43// 2026-09-05 it twice took a 31 GB machine to global OOM -- 13.9 GB, then
44// 16.1 GB resident -- killing the session around it. With the inputs as
45// literals glslc also tried to constant-fold every chain, which is worse than
46// slow: a folded case is the COMPILER's arithmetic, not the GPU's, so the gate
47// quietly stops involving the machine it exists to test.
48//
49// So: ONE record per case in a buffer, ONE dispatcher in the shader, cases run
50// across invocations. Shader size is O(number of node families), not O(number
51// of cases), and every input is opaque to the optimiser. Adding a thousand
52// cases now costs a thousand records and not one line of GLSL.
53//
54// The opcode constants are EMITTED INTO THE SHADER from the C++ enum below, so
55// the two dispatchers cannot drift apart by editing one of them.
56//
57// And the process caps its own address space (see MEM_LIMIT_BYTES): if this
58// ever blows up again it dies alone with a clear message instead of taking the
59// machine with it.
60// ---------------------------------------------------------------------------
61//
62// Build (no engine, no godot-cpp, no scons):
63// g++ -O2 -std=c++17 -I src/shared \
64// -o /tmp/noise_parity tests/test_shared_noise_parity.cpp -lvulkan
65//
66// Run:
67// /tmp/noise_parity # default subjects
68// /tmp/noise_parity <file> <marker> [...] # override, in dependency order
69//
70// Exit status is 0 when every case agrees.
71// ============================================================================
72
73#include <vulkan/vulkan.h>
74#include <sys/resource.h>
75
76#include <cmath>
77#include <cstdint>
78#include <cstdio>
79#include <cstdlib>
80#include <cstring>
81#include <fstream>
82#include <sstream>
83#include <string>
84#include <utility>
85#include <vector>
86
87#include "blender_noise_cpp_shim.h"
88
89using namespace godot::bnoisegl;
90
91// hash FIRST: perlin's lattice gradients are that hash, and GLSL has no
92// #include to say so. Same order the C++ shim uses and the emitter emits.
93static const char *SUBJECT_FILES[] = {
94 "src/gpu/blender_hash_glsl.gen.h",
95 "src/gpu/blender_perlin_glsl.gen.h",
96};
97static const char *SUBJECT_MARKS[] = { "BLENDER_HASH_GLSL", "BLENDER_PERLIN_GLSL" };
98static const int SUBJECT_N = 2;
99
100static const double TOL_REL = 1e-5;
101static const double TOL_ABS = 1e-6;
102
103// 4 GiB. A healthy run of this gate peaks around a tenth of that; the headroom
104// is for the Vulkan driver's own allocations, which are not small and are not
105// ours to predict. The point is not to be tight, it is to be BOUNDED.
106static const rlim_t MEM_LIMIT_BYTES = (rlim_t)4 * 1024 * 1024 * 1024;
107
108static void cap_own_memory() {
109 struct rlimit rl;
110 if (getrlimit(RLIMIT_AS, &rl) != 0) {
111 fprintf(stderr, "WARNING: getrlimit failed; running with NO memory cap\n");
112 return;
113 }
114 if (rl.rlim_cur != RLIM_INFINITY && rl.rlim_cur <= MEM_LIMIT_BYTES) {
115 return; // already at least as strict as we want
116 }
117 const rlim_t want = (rl.rlim_max == RLIM_INFINITY || rl.rlim_max > MEM_LIMIT_BYTES)
118 ? MEM_LIMIT_BYTES
119 : rl.rlim_max;
120 rl.rlim_cur = want;
121 if (setrlimit(RLIMIT_AS, &rl) != 0) {
122 fprintf(stderr, "WARNING: setrlimit failed; running with NO memory cap\n");
123 return;
124 }
125 printf("memcap : %.1f GiB address space (this gate OOM-killed the machine "
126 "twice before it was data-driven)\n", (double)want / (1024.0 * 1024.0 * 1024.0));
127}
128
129static void die(const char *what, VkResult r) {
130 fprintf(stderr, "FATAL: %s -> VkResult %d\n", what, (int)r);
131 // ALWAYS name the cap, not only on VK_ERROR_OUT_OF_HOST_MEMORY. Starving
132 // this process shows up as VK_ERROR_INITIALIZATION_FAILED (-3) from
133 // vkEnumeratePhysicalDevices long before anyone gets to allocate anything,
134 // and a reader who does not know a cap is in force will go looking at the
135 // driver. Measured: at `ulimit -v 200000` that is exactly what happens.
136 struct rlimit rl;
137 if (getrlimit(RLIMIT_AS, &rl) == 0 && rl.rlim_cur != RLIM_INFINITY) {
138 fprintf(stderr, " This process is capped at %.1f GiB of address space.\n"
139 " The cap is deliberate: without it this gate twice took a\n"
140 " 31 GB machine to global OOM. If it is being hit, check\n"
141 " whether the shader has grown back into something whose\n"
142 " size depends on the case count -- fix that, do not raise\n"
143 " the cap.\n",
144 (double)rl.rlim_cur / (1024.0 * 1024.0 * 1024.0));
145 }
146 exit(1);
147}
148#define VK(x) do { VkResult _r = (x); if (_r != VK_SUCCESS) die(#x, _r); } while (0)
149
150static std::string read_file(const std::string &path) {
151 std::ifstream f(path);
152 if (!f) { fprintf(stderr, "FATAL: cannot read %s\n", path.c_str()); exit(1); }
153 std::stringstream ss;
154 ss << f.rdbuf();
155 return ss.str();
156}
157
158// Pull the GLSL out of a C++ raw string literal that follows `marker`, reading
159// the delimiter off the text rather than assuming R"( -- the generated headers
160// carry one because their payload is GLSL full of parentheses.
161static std::string extract_raw_string(const std::string &src, const std::string &marker) {
162 size_t m = src.find(marker);
163 if (m == std::string::npos) {
164 fprintf(stderr, "FATAL: marker '%s' not found\n", marker.c_str());
165 exit(1);
166 }
167 size_t rq = src.find("R\"", m);
168 if (rq == std::string::npos) {
169 fprintf(stderr, "FATAL: no raw string after '%s'\n", marker.c_str());
170 exit(1);
171 }
172 size_t lp = src.find('(', rq);
173 if (lp == std::string::npos) {
174 fprintf(stderr, "FATAL: malformed raw string after '%s'\n", marker.c_str());
175 exit(1);
176 }
177 const std::string delim = src.substr(rq + 2, lp - (rq + 2));
178 const std::string term = ")" + delim + "\"";
179 size_t close = src.find(term, lp + 1);
180 if (close == std::string::npos) {
181 fprintf(stderr, "FATAL: unterminated raw string (delimiter '%s') after '%s'\n",
182 delim.c_str(), marker.c_str());
183 exit(1);
184 }
185 return src.substr(lp + 1, close - (lp + 1));
186}
187
188static std::vector<uint32_t> compile_glsl(const std::string &src) {
189 char in_path[] = "/tmp/proc_noise_parity_XXXXXX";
190 int fd = mkstemp(in_path);
191 if (fd < 0) { perror("mkstemp"); exit(1); }
192 FILE *f = fdopen(fd, "w");
193 fwrite(src.data(), 1, src.size(), f);
194 fclose(f);
195 std::string spv = std::string(in_path) + ".spv";
196 std::string cmd = "glslc -fshader-stage=comp -o " + spv + " " + in_path;
197 if (system(cmd.c_str()) != 0) {
198 fprintf(stderr, "FATAL: glslc failed (source left at %s)\n", in_path);
199 exit(1);
200 }
201 FILE *sf = fopen(spv.c_str(), "rb");
202 fseek(sf, 0, SEEK_END); long n = ftell(sf); fseek(sf, 0, SEEK_SET);
203 std::vector<uint32_t> out(n / 4);
204 if (fread(out.data(), 1, n, sf) != (size_t)n) { fprintf(stderr, "FATAL: short read\n"); exit(1); }
205 fclose(sf);
206 remove(in_path); remove(spv.c_str());
207 return out;
208}
209
210struct Ctx {
211 VkInstance instance = VK_NULL_HANDLE;
212 VkPhysicalDevice phys = VK_NULL_HANDLE;
213 VkDevice dev = VK_NULL_HANDLE;
214 VkQueue queue = VK_NULL_HANDLE;
215 uint32_t qfam = 0;
216 VkCommandPool cmdpool = VK_NULL_HANDLE;
217 VkPhysicalDeviceMemoryProperties memprops{};
218 char devname[256] = {0};
219};
220
221static uint32_t find_mem(Ctx &c, uint32_t bits, VkMemoryPropertyFlags want) {
222 for (uint32_t i = 0; i < c.memprops.memoryTypeCount; i++) {
223 if ((bits & (1u << i)) && (c.memprops.memoryTypes[i].propertyFlags & want) == want) {
224 return i;
225 }
226 }
227 fprintf(stderr, "FATAL: no memory type with flags 0x%x\n", want);
228 exit(1);
229}
230
231static void init_vulkan(Ctx &c) {
232 VkApplicationInfo app{VK_STRUCTURE_TYPE_APPLICATION_INFO};
233 app.pApplicationName = "proc-nodes shared-noise parity";
234 app.apiVersion = VK_API_VERSION_1_1;
235 VkInstanceCreateInfo ii{VK_STRUCTURE_TYPE_INSTANCE_CREATE_INFO};
236 ii.pApplicationInfo = &app;
237 VK(vkCreateInstance(&ii, nullptr, &c.instance));
238
239 uint32_t n = 0;
240 VK(vkEnumeratePhysicalDevices(c.instance, &n, nullptr));
241 if (!n) { fprintf(stderr, "FATAL: no Vulkan device\n"); exit(1); }
242 std::vector<VkPhysicalDevice> devs(n);
243 VK(vkEnumeratePhysicalDevices(c.instance, &n, devs.data()));
244 c.phys = devs[0];
245 for (size_t i = 0; i < devs.size(); i++) {
246 VkPhysicalDeviceProperties p;
247 vkGetPhysicalDeviceProperties(devs[i], &p);
248 if (p.deviceType == VK_PHYSICAL_DEVICE_TYPE_DISCRETE_GPU) { c.phys = devs[i]; break; }
249 }
250 VkPhysicalDeviceProperties props;
251 vkGetPhysicalDeviceProperties(c.phys, &props);
252 snprintf(c.devname, sizeof(c.devname), "%s", props.deviceName);
253 vkGetPhysicalDeviceMemoryProperties(c.phys, &c.memprops);
254
255 uint32_t qn = 0;
256 vkGetPhysicalDeviceQueueFamilyProperties(c.phys, &qn, nullptr);
257 std::vector<VkQueueFamilyProperties> qs(qn);
258 vkGetPhysicalDeviceQueueFamilyProperties(c.phys, &qn, qs.data());
259 bool found = false;
260 for (uint32_t i = 0; i < qn; i++) {
261 if (qs[i].queueFlags & VK_QUEUE_COMPUTE_BIT) { c.qfam = i; found = true; break; }
262 }
263 if (!found) { fprintf(stderr, "FATAL: no compute queue\n"); exit(1); }
264
265 float prio = 1.0f;
266 VkDeviceQueueCreateInfo qi{VK_STRUCTURE_TYPE_DEVICE_QUEUE_CREATE_INFO};
267 qi.queueFamilyIndex = c.qfam; qi.queueCount = 1; qi.pQueuePriorities = &prio;
268 VkDeviceCreateInfo di{VK_STRUCTURE_TYPE_DEVICE_CREATE_INFO};
269 di.queueCreateInfoCount = 1; di.pQueueCreateInfos = &qi;
270 VK(vkCreateDevice(c.phys, &di, nullptr, &c.dev));
271 vkGetDeviceQueue(c.dev, c.qfam, 0, &c.queue);
272
273 VkCommandPoolCreateInfo ci{VK_STRUCTURE_TYPE_COMMAND_POOL_CREATE_INFO};
274 ci.queueFamilyIndex = c.qfam;
275 ci.flags = VK_COMMAND_POOL_CREATE_RESET_COMMAND_BUFFER_BIT;
276 VK(vkCreateCommandPool(c.dev, &ci, nullptr, &c.cmdpool));
277}
278
279// ============================================================================
280// THE CASE RECORD
281//
282// One fixed-size record per case, read by both dispatchers. Every numeric
283// input travels here, so the shader compiler cannot fold it and the GPU has to
284// actually run. Only the opcode and the dimension pick a code path.
285// ============================================================================
286
287static const uint32_t STRIDE = 16;
288
289enum Op {
290 OP_HASH1 = 1, OP_HASH2, OP_HASH3, OP_HASH4,
291 OP_UINT_TO_FLOAT_01,
292 OP_HASH_TO_FLOAT1, OP_HASH_TO_FLOAT2, OP_HASH_TO_FLOAT3, OP_HASH_TO_FLOAT4,
293 OP_HASH_FLOAT, // dim 1..4
294 OP_HFF, // dim 1..4
295 OP_HFF2, // dim 2..4, comp 0..1
296 OP_HFF3, // dim 1..4, comp 0..2
297 OP_HFF4, // comp 0..3
298 OP_WRAP_MUL, OP_WRAP_ADD,
299 OP_INT_TO_FLOAT_01,
300 OP_PCG2D, OP_PCG3D, OP_PCG4D, // comp
301 OP_INT_TO_FLOAT2, OP_INT_TO_FLOAT3_I3, OP_INT_TO_FLOAT4,
302 OP_INT_TO_FLOAT3_I2, OP_INT_TO_FLOAT3_I4,
303 OP_PERLIN_NOISE, // dim
304 OP_PERLIN_SIGNED, // dim
305 OP_PERLIN_SELECT, // dim, type, flags
306 OP_PERLIN_DISTORTED, // dim, type, flags
307 OP_PERLIN_FLOAT3, // dim, comp, type, flags
308 OP__COUNT
309};
310
311// Emitted into the shader so the two dispatchers cannot drift.
312struct OpName { Op op; const char *name; };
313static const OpName OP_NAMES[] = {
314 { OP_HASH1, "OP_HASH1" }, { OP_HASH2, "OP_HASH2" }, { OP_HASH3, "OP_HASH3" },
315 { OP_HASH4, "OP_HASH4" }, { OP_UINT_TO_FLOAT_01, "OP_UINT_TO_FLOAT_01" },
316 { OP_HASH_TO_FLOAT1, "OP_HASH_TO_FLOAT1" }, { OP_HASH_TO_FLOAT2, "OP_HASH_TO_FLOAT2" },
317 { OP_HASH_TO_FLOAT3, "OP_HASH_TO_FLOAT3" }, { OP_HASH_TO_FLOAT4, "OP_HASH_TO_FLOAT4" },
318 { OP_HASH_FLOAT, "OP_HASH_FLOAT" }, { OP_HFF, "OP_HFF" }, { OP_HFF2, "OP_HFF2" },
319 { OP_HFF3, "OP_HFF3" }, { OP_HFF4, "OP_HFF4" },
320 { OP_WRAP_MUL, "OP_WRAP_MUL" }, { OP_WRAP_ADD, "OP_WRAP_ADD" },
321 { OP_INT_TO_FLOAT_01, "OP_INT_TO_FLOAT_01" },
322 { OP_PCG2D, "OP_PCG2D" }, { OP_PCG3D, "OP_PCG3D" }, { OP_PCG4D, "OP_PCG4D" },
323 { OP_INT_TO_FLOAT2, "OP_INT_TO_FLOAT2" }, { OP_INT_TO_FLOAT3_I3, "OP_INT_TO_FLOAT3_I3" },
324 { OP_INT_TO_FLOAT4, "OP_INT_TO_FLOAT4" }, { OP_INT_TO_FLOAT3_I2, "OP_INT_TO_FLOAT3_I2" },
325 { OP_INT_TO_FLOAT3_I4, "OP_INT_TO_FLOAT3_I4" },
326 { OP_PERLIN_NOISE, "OP_PERLIN_NOISE" }, { OP_PERLIN_SIGNED, "OP_PERLIN_SIGNED" },
327 { OP_PERLIN_SELECT, "OP_PERLIN_SELECT" }, { OP_PERLIN_DISTORTED, "OP_PERLIN_DISTORTED" },
328 { OP_PERLIN_FLOAT3, "OP_PERLIN_FLOAT3" },
329};
330
331struct Rec {
332 uint32_t w[STRIDE];
333 std::string name;
334 bool exact;
335 uint32_t cpu;
336};
337
338static uint32_t bits_of(float f) { uint32_t u; memcpy(&u, &f, sizeof(u)); return u; }
339static float float_of(uint32_t u) { float f; memcpy(&f, &u, sizeof(f)); return f; }
340
341// Word slots, named once so both sides agree by construction.
342enum { W_OP = 0, W_DIM = 1, W_TYPE = 2, W_FLAGS = 3, W_COMP = 4,
343 W_A = 5, W_B = 6, W_C = 7, W_D = 8,
344 W_DETAIL = 9, W_ROUGH = 10, W_LAC = 11, W_OFF = 12, W_GAIN = 13, W_DIST = 14 };
345
346// --- the CPU half of the dispatcher -----------------------------------------
347static uint32_t eval_cpu(const uint32_t *w) {
348 const uint32_t op = w[W_OP], dim = w[W_DIM], comp = w[W_COMP];
349 const int type = (int)w[W_TYPE];
350 const bool nrm = (w[W_FLAGS] & 1u) != 0u;
351 const uint32_t ua = w[W_A], ub = w[W_B], uc = w[W_C], ud = w[W_D];
352 const int32_t ia = (int32_t)ua, ib = (int32_t)ub, ic = (int32_t)uc, id = (int32_t)ud;
353 const float x = float_of(ua), y = float_of(ub), z = float_of(uc), wv = float_of(ud);
354 const float detail = float_of(w[W_DETAIL]), rough = float_of(w[W_ROUGH]);
355 const float lac = float_of(w[W_LAC]), off = float_of(w[W_OFF]);
356 const float gain = float_of(w[W_GAIN]), dist = float_of(w[W_DIST]);
357 const vec2 v2(x, y); const vec3 v3(x, y, z); const vec4 v4(x, y, z, wv);
358 const ivec2 i2(ia, ib); const ivec3 i3(ia, ib, ic); const ivec4 i4(ia, ib, ic, id);
359
360 switch (op) {
361 case OP_HASH1: return bn_hash(ua);
362 case OP_HASH2: return bn_hash(ua, ub);
363 case OP_HASH3: return bn_hash(ua, ub, uc);
364 case OP_HASH4: return bn_hash(ua, ub, uc, ud);
365 case OP_UINT_TO_FLOAT_01: return bits_of(bn_uint_to_float_01(ua));
366 case OP_HASH_TO_FLOAT1: return bits_of(bn_hash_to_float(ua));
367 case OP_HASH_TO_FLOAT2: return bits_of(bn_hash_to_float(ua, ub));
368 case OP_HASH_TO_FLOAT3: return bits_of(bn_hash_to_float(ua, ub, uc));
369 case OP_HASH_TO_FLOAT4: return bits_of(bn_hash_to_float(ua, ub, uc, ud));
370 case OP_HASH_FLOAT:
371 if (dim == 1) return bn_hash_float(x);
372 if (dim == 2) return bn_hash_float(v2);
373 if (dim == 3) return bn_hash_float(v3);
374 return bn_hash_float(v4);
375 case OP_HFF:
376 if (dim == 1) return bits_of(bn_hash_float_to_float(x));
377 if (dim == 2) return bits_of(bn_hash_float_to_float(v2));
378 if (dim == 3) return bits_of(bn_hash_float_to_float(v3));
379 return bits_of(bn_hash_float_to_float(v4));
380 case OP_HFF2: {
381 vec2 r = (dim == 2) ? bn_hash_float_to_float2(v2)
382 : (dim == 3) ? bn_hash_float_to_float2(v3) : bn_hash_float_to_float2(v4);
383 return bits_of(comp == 0 ? r.x : r.y);
384 }
385 case OP_HFF3: {
386 vec3 r = (dim == 1) ? bn_hash_float_to_float3(x)
387 : (dim == 2) ? bn_hash_float_to_float3(v2)
388 : (dim == 3) ? bn_hash_float_to_float3(v3) : bn_hash_float_to_float3(v4);
389 return bits_of(comp == 0 ? r.x : (comp == 1 ? r.y : r.z));
390 }
391 case OP_HFF4: {
392 vec4 r = bn_hash_float_to_float4(v4);
393 return bits_of(comp == 0 ? r.x : (comp == 1 ? r.y : (comp == 2 ? r.z : r.w)));
394 }
395 case OP_WRAP_MUL: return (uint32_t)bn_wrap_mul(ia, ib);
396 case OP_WRAP_ADD: return (uint32_t)bn_wrap_add(ia, ib);
397 case OP_INT_TO_FLOAT_01: return bits_of(bn_int_to_float_01(ia));
398 case OP_PCG2D: { ivec2 r = bn_hash_pcg2d_i(i2); return (uint32_t)(comp == 0 ? r.x : r.y); }
399 case OP_PCG3D: { ivec3 r = bn_hash_pcg3d_i(i3);
400 return (uint32_t)(comp == 0 ? r.x : (comp == 1 ? r.y : r.z)); }
401 case OP_PCG4D: { ivec4 r = bn_hash_pcg4d_i(i4);
402 return (uint32_t)(comp == 0 ? r.x : (comp == 1 ? r.y : (comp == 2 ? r.z : r.w))); }
403 case OP_INT_TO_FLOAT2: { vec2 r = bn_hash_int_to_float2(i2);
404 return bits_of(comp == 0 ? r.x : r.y); }
405 case OP_INT_TO_FLOAT3_I3: { vec3 r = bn_hash_int_to_float3(i3);
406 return bits_of(comp == 0 ? r.x : (comp == 1 ? r.y : r.z)); }
407 case OP_INT_TO_FLOAT4: { vec4 r = bn_hash_int_to_float4(i4);
408 return bits_of(comp == 0 ? r.x : (comp == 1 ? r.y : (comp == 2 ? r.z : r.w))); }
409 case OP_INT_TO_FLOAT3_I2: { vec3 r = bn_hash_int_to_float3(i2);
410 return bits_of(comp == 0 ? r.x : (comp == 1 ? r.y : r.z)); }
411 case OP_INT_TO_FLOAT3_I4: { vec3 r = bn_hash_int_to_float3(i4);
412 return bits_of(comp == 0 ? r.x : (comp == 1 ? r.y : r.z)); }
413 case OP_PERLIN_NOISE:
414 if (dim == 1) return bits_of(bp_perlin_noise(x));
415 if (dim == 2) return bits_of(bp_perlin_noise(v2));
416 if (dim == 3) return bits_of(bp_perlin_noise(v3));
417 return bits_of(bp_perlin_noise(v4));
418 case OP_PERLIN_SIGNED:
419 if (dim == 1) return bits_of(bp_perlin_signed(x));
420 if (dim == 2) return bits_of(bp_perlin_signed(v2));
421 if (dim == 3) return bits_of(bp_perlin_signed(v3));
422 return bits_of(bp_perlin_signed(v4));
423 case OP_PERLIN_SELECT:
424 if (dim == 1) return bits_of(bp_perlin_select_1(x, detail, rough, lac, off, gain, type, nrm));
425 if (dim == 2) return bits_of(bp_perlin_select_2(v2, detail, rough, lac, off, gain, type, nrm));
426 if (dim == 3) return bits_of(bp_perlin_select_3(v3, detail, rough, lac, off, gain, type, nrm));
427 return bits_of(bp_perlin_select_4(v4, detail, rough, lac, off, gain, type, nrm));
428 case OP_PERLIN_DISTORTED:
429 if (dim == 1) return bits_of(bp_perlin_fractal_distorted_1(x, detail, rough, lac, off, gain, dist, type, nrm));
430 if (dim == 2) return bits_of(bp_perlin_fractal_distorted_2(v2, detail, rough, lac, off, gain, dist, type, nrm));
431 if (dim == 3) return bits_of(bp_perlin_fractal_distorted_3(v3, detail, rough, lac, off, gain, dist, type, nrm));
432 return bits_of(bp_perlin_fractal_distorted_4(v4, detail, rough, lac, off, gain, dist, type, nrm));
433 case OP_PERLIN_FLOAT3: {
434 vec3 r = (dim == 1) ? bp_perlin_float3_fractal_distorted(x, detail, rough, lac, off, gain, dist, type, nrm)
435 : (dim == 2) ? bp_perlin_float3_fractal_distorted(v2, detail, rough, lac, off, gain, dist, type, nrm)
436 : (dim == 3) ? bp_perlin_float3_fractal_distorted(v3, detail, rough, lac, off, gain, dist, type, nrm)
437 : bp_perlin_float3_fractal_distorted(v4, detail, rough, lac, off, gain, dist, type, nrm);
438 return bits_of(comp == 0 ? r.x : (comp == 1 ? r.y : r.z));
439 }
440 default:
441 fprintf(stderr, "FATAL: cpu dispatcher has no arm for op %u\n", op);
442 exit(1);
443 }
444}
445
446// --- the GLSL half. O(families), not O(cases). ------------------------------
447static const char *DISPATCH_GLSL = R"DISPATCH(
448layout(local_size_x = 64) in;
449layout(std430, binding = 0) buffer Out { uint v[]; } out_v;
450layout(std430, binding = 1) readonly buffer In { uint v[]; } in_v;
451
452uint gW(uint b, uint k) { return in_v.v[b + k]; }
453float gF(uint b, uint k) { return uintBitsToFloat(in_v.v[b + k]); }
454
455void main() {
456 uint idx = gl_GlobalInvocationID.x;
457 if (idx >= CASE_COUNT) { return; }
458 uint b = idx * STRIDE_W;
459
460 uint op = gW(b, 0u);
461 uint dim = gW(b, 1u);
462 int type = int(gW(b, 2u));
463 bool nrm = (gW(b, 3u) & 1u) != 0u;
464 uint comp = gW(b, 4u);
465
466 uint ua = gW(b, 5u), ub = gW(b, 6u), uc = gW(b, 7u), ud = gW(b, 8u);
467 int ia = int(ua), ib = int(ub), ic = int(uc), id = int(ud);
468 float x = gF(b, 5u), y = gF(b, 6u), z = gF(b, 7u), w = gF(b, 8u);
469 float detail = gF(b, 9u), rough = gF(b, 10u), lac = gF(b, 11u);
470 float off = gF(b, 12u), gain = gF(b, 13u), dist = gF(b, 14u);
471
472 vec2 v2 = vec2(x, y); vec3 v3 = vec3(x, y, z); vec4 v4 = vec4(x, y, z, w);
473 ivec2 i2 = ivec2(ia, ib); ivec3 i3 = ivec3(ia, ib, ic); ivec4 i4 = ivec4(ia, ib, ic, id);
474
475 uint r = 0u;
476 if (op == OP_HASH1) { r = bn_hash(ua); }
477 else if (op == OP_HASH2) { r = bn_hash(ua, ub); }
478 else if (op == OP_HASH3) { r = bn_hash(ua, ub, uc); }
479 else if (op == OP_HASH4) { r = bn_hash(ua, ub, uc, ud); }
480 else if (op == OP_UINT_TO_FLOAT_01) { r = floatBitsToUint(bn_uint_to_float_01(ua)); }
481 else if (op == OP_HASH_TO_FLOAT1) { r = floatBitsToUint(bn_hash_to_float(ua)); }
482 else if (op == OP_HASH_TO_FLOAT2) { r = floatBitsToUint(bn_hash_to_float(ua, ub)); }
483 else if (op == OP_HASH_TO_FLOAT3) { r = floatBitsToUint(bn_hash_to_float(ua, ub, uc)); }
484 else if (op == OP_HASH_TO_FLOAT4) { r = floatBitsToUint(bn_hash_to_float(ua, ub, uc, ud)); }
485 else if (op == OP_HASH_FLOAT) {
486 if (dim == 1u) { r = bn_hash_float(x); }
487 else if (dim == 2u) { r = bn_hash_float(v2); }
488 else if (dim == 3u) { r = bn_hash_float(v3); }
489 else { r = bn_hash_float(v4); }
490 }
491 else if (op == OP_HFF) {
492 float f;
493 if (dim == 1u) { f = bn_hash_float_to_float(x); }
494 else if (dim == 2u) { f = bn_hash_float_to_float(v2); }
495 else if (dim == 3u) { f = bn_hash_float_to_float(v3); }
496 else { f = bn_hash_float_to_float(v4); }
497 r = floatBitsToUint(f);
498 }
499 else if (op == OP_HFF2) {
500 vec2 q;
501 if (dim == 2u) { q = bn_hash_float_to_float2(v2); }
502 else if (dim == 3u) { q = bn_hash_float_to_float2(v3); }
503 else { q = bn_hash_float_to_float2(v4); }
504 r = floatBitsToUint(comp == 0u ? q.x : q.y);
505 }
506 else if (op == OP_HFF3) {
507 vec3 q;
508 if (dim == 1u) { q = bn_hash_float_to_float3(x); }
509 else if (dim == 2u) { q = bn_hash_float_to_float3(v2); }
510 else if (dim == 3u) { q = bn_hash_float_to_float3(v3); }
511 else { q = bn_hash_float_to_float3(v4); }
512 r = floatBitsToUint(comp == 0u ? q.x : (comp == 1u ? q.y : q.z));
513 }
514 else if (op == OP_HFF4) {
515 vec4 q = bn_hash_float_to_float4(v4);
516 r = floatBitsToUint(comp == 0u ? q.x : (comp == 1u ? q.y : (comp == 2u ? q.z : q.w)));
517 }
518 else if (op == OP_WRAP_MUL) { r = uint(bn_wrap_mul(ia, ib)); }
519 else if (op == OP_WRAP_ADD) { r = uint(bn_wrap_add(ia, ib)); }
520 else if (op == OP_INT_TO_FLOAT_01) { r = floatBitsToUint(bn_int_to_float_01(ia)); }
521 else if (op == OP_PCG2D) { ivec2 q = bn_hash_pcg2d_i(i2); r = uint(comp == 0u ? q.x : q.y); }
522 else if (op == OP_PCG3D) { ivec3 q = bn_hash_pcg3d_i(i3);
523 r = uint(comp == 0u ? q.x : (comp == 1u ? q.y : q.z)); }
524 else if (op == OP_PCG4D) { ivec4 q = bn_hash_pcg4d_i(i4);
525 r = uint(comp == 0u ? q.x : (comp == 1u ? q.y : (comp == 2u ? q.z : q.w))); }
526 else if (op == OP_INT_TO_FLOAT2) { vec2 q = bn_hash_int_to_float2(i2);
527 r = floatBitsToUint(comp == 0u ? q.x : q.y); }
528 else if (op == OP_INT_TO_FLOAT3_I3) { vec3 q = bn_hash_int_to_float3(i3);
529 r = floatBitsToUint(comp == 0u ? q.x : (comp == 1u ? q.y : q.z)); }
530 else if (op == OP_INT_TO_FLOAT4) { vec4 q = bn_hash_int_to_float4(i4);
531 r = floatBitsToUint(comp == 0u ? q.x : (comp == 1u ? q.y : (comp == 2u ? q.z : q.w))); }
532 else if (op == OP_INT_TO_FLOAT3_I2) { vec3 q = bn_hash_int_to_float3(i2);
533 r = floatBitsToUint(comp == 0u ? q.x : (comp == 1u ? q.y : q.z)); }
534 else if (op == OP_INT_TO_FLOAT3_I4) { vec3 q = bn_hash_int_to_float3(i4);
535 r = floatBitsToUint(comp == 0u ? q.x : (comp == 1u ? q.y : q.z)); }
536 else if (op == OP_PERLIN_NOISE) {
537 float f;
538 if (dim == 1u) { f = bp_perlin_noise(x); }
539 else if (dim == 2u) { f = bp_perlin_noise(v2); }
540 else if (dim == 3u) { f = bp_perlin_noise(v3); }
541 else { f = bp_perlin_noise(v4); }
542 r = floatBitsToUint(f);
543 }
544 else if (op == OP_PERLIN_SIGNED) {
545 float f;
546 if (dim == 1u) { f = bp_perlin_signed(x); }
547 else if (dim == 2u) { f = bp_perlin_signed(v2); }
548 else if (dim == 3u) { f = bp_perlin_signed(v3); }
549 else { f = bp_perlin_signed(v4); }
550 r = floatBitsToUint(f);
551 }
552 else if (op == OP_PERLIN_SELECT) {
553 float f;
554 if (dim == 1u) { f = bp_perlin_select_1(x, detail, rough, lac, off, gain, type, nrm); }
555 else if (dim == 2u) { f = bp_perlin_select_2(v2, detail, rough, lac, off, gain, type, nrm); }
556 else if (dim == 3u) { f = bp_perlin_select_3(v3, detail, rough, lac, off, gain, type, nrm); }
557 else { f = bp_perlin_select_4(v4, detail, rough, lac, off, gain, type, nrm); }
558 r = floatBitsToUint(f);
559 }
560 else if (op == OP_PERLIN_DISTORTED) {
561 float f;
562 if (dim == 1u) { f = bp_perlin_fractal_distorted_1(x, detail, rough, lac, off, gain, dist, type, nrm); }
563 else if (dim == 2u) { f = bp_perlin_fractal_distorted_2(v2, detail, rough, lac, off, gain, dist, type, nrm); }
564 else if (dim == 3u) { f = bp_perlin_fractal_distorted_3(v3, detail, rough, lac, off, gain, dist, type, nrm); }
565 else { f = bp_perlin_fractal_distorted_4(v4, detail, rough, lac, off, gain, dist, type, nrm); }
566 r = floatBitsToUint(f);
567 }
568 else if (op == OP_PERLIN_FLOAT3) {
569 vec3 q;
570 if (dim == 1u) { q = bp_perlin_float3_fractal_distorted(x, detail, rough, lac, off, gain, dist, type, nrm); }
571 else if (dim == 2u) { q = bp_perlin_float3_fractal_distorted(v2, detail, rough, lac, off, gain, dist, type, nrm); }
572 else if (dim == 3u) { q = bp_perlin_float3_fractal_distorted(v3, detail, rough, lac, off, gain, dist, type, nrm); }
573 else { q = bp_perlin_float3_fractal_distorted(v4, detail, rough, lac, off, gain, dist, type, nrm); }
574 r = floatBitsToUint(comp == 0u ? q.x : (comp == 1u ? q.y : q.z));
575 }
576 out_v.v[idx] = r;
577}
578)DISPATCH";
579
580// ============================================================================
581// THE CASES
582//
583// Inputs are chosen where these functions actually break: zero, the all-ones
584// word a shift can eat, single-bit words that expose a rotate written as a
585// shift, the two's-complement spelling of a negative lattice coordinate (an
586// ORDINARY input -- Blender indexes its lattice with signed ints cast to
587// uint), real float bit patterns, and for perlin a row past the 100000 wrap
588// where the truncated modulo and the half-unit nudge past a million do
589// something.
590// ============================================================================
591
592struct B {
593 uint32_t w[STRIDE];
594 explicit B(uint32_t op) {
595 memset(w, 0, sizeof(w));
596 w[W_OP] = op;
597 w[W_DIM] = 3;
598 }
599 B &dim(uint32_t d) { w[W_DIM] = d; return *this; }
600 B &type(int t) { w[W_TYPE] = (uint32_t)t; return *this; }
601 B &norm(bool n) { w[W_FLAGS] = n ? 1u : 0u; return *this; }
602 B &comp(uint32_t c) { w[W_COMP] = c; return *this; }
603 B &u(uint32_t a, uint32_t b = 0, uint32_t c = 0, uint32_t d = 0) {
604 w[W_A] = a; w[W_B] = b; w[W_C] = c; w[W_D] = d; return *this;
605 }
606 B &f(float a, float b = 0, float c = 0, float d = 0) {
607 w[W_A] = bits_of(a); w[W_B] = bits_of(b);
608 w[W_C] = bits_of(c); w[W_D] = bits_of(d); return *this;
609 }
610 B &frac(float detail, float rough, float lac, float off, float gain, float dist) {
611 w[W_DETAIL] = bits_of(detail); w[W_ROUGH] = bits_of(rough);
612 w[W_LAC] = bits_of(lac); w[W_OFF] = bits_of(off);
613 w[W_GAIN] = bits_of(gain); w[W_DIST] = bits_of(dist); return *this;
614 }
615};
616
617static std::vector<Rec> g_cases;
618
619static void add(const std::string &name, bool exact, const B &b) {
620 Rec r;
621 memcpy(r.w, b.w, sizeof(r.w));
622 r.name = name;
623 r.exact = exact;
624 r.cpu = eval_cpu(r.w);
625 g_cases.push_back(r);
626}
627
628static void build_cases() {
629 const uint32_t KU[] = {
630 0u, 1u, 2u, 7u, 16u, 255u,
631 0x0000FFFFu, 0xFFFF0000u, 0x80000000u, 0xFFFFFFFFu,
632 0xdeadbeefu, 0xcafebabeu, 0x12345678u,
633 (uint32_t)-1, (uint32_t)-7, (uint32_t)-1000000
634 };
635 const int NU = (int)(sizeof(KU) / sizeof(KU[0]));
636 const float KF[] = {
637 0.0f, -0.0f, 1.0f, -1.0f, 0.5f, -0.25f, 2.5f, 7.0f,
638 3.14159265f, -7.25f, 1e7f, -1e7f, 1e-7f, 123456.789f, -0.001f, 65536.0f
639 };
640 const int NF = (int)(sizeof(KF) / sizeof(KF[0]));
641
642 // --- EXACT: the hash layer.
643 for (int i = 0; i < NU; i++) {
644 const uint32_t a = KU[i], b = KU[(i + 3) % NU], c = KU[(i + 7) % NU], d = KU[(i + 11) % NU];
645 add("bn_hash1", true, B(OP_HASH1).u(a));
646 add("bn_hash2", true, B(OP_HASH2).u(a, b));
647 add("bn_hash3", true, B(OP_HASH3).u(a, b, c));
648 add("bn_hash4", true, B(OP_HASH4).u(a, b, c, d));
649 add("bn_uint_to_float_01", true, B(OP_UINT_TO_FLOAT_01).u(a));
650 add("bn_hash_to_float1", true, B(OP_HASH_TO_FLOAT1).u(a));
651 add("bn_hash_to_float2", true, B(OP_HASH_TO_FLOAT2).u(a, b));
652 add("bn_hash_to_float3", true, B(OP_HASH_TO_FLOAT3).u(a, b, c));
653 add("bn_hash_to_float4", true, B(OP_HASH_TO_FLOAT4).u(a, b, c, d));
654
655 // PCG. Signed throughout, so the shift is ARITHMETIC and the wrap goes
656 // through uint on both sides -- neither language defines signed overflow.
657 add("bn_wrap_mul", true, B(OP_WRAP_MUL).u(a, b));
658 add("bn_wrap_add", true, B(OP_WRAP_ADD).u(a, b));
659 add("bn_int_to_float_01", true, B(OP_INT_TO_FLOAT_01).u(a));
660 for (uint32_t k = 0; k < 2; k++) {
661 add("bn_hash_pcg2d_i", true, B(OP_PCG2D).u(a, b).comp(k));
662 add("bn_hash_int_to_float2", true, B(OP_INT_TO_FLOAT2).u(a, b).comp(k));
663 }
664 for (uint32_t k = 0; k < 3; k++) {
665 add("bn_hash_pcg3d_i", true, B(OP_PCG3D).u(a, b, c).comp(k));
666 add("bn_hash_int_to_float3(i3)", true, B(OP_INT_TO_FLOAT3_I3).u(a, b, c).comp(k));
667 add("bn_hash_int_to_float3(i2)", true, B(OP_INT_TO_FLOAT3_I2).u(a, b).comp(k));
668 add("bn_hash_int_to_float3(i4)", true, B(OP_INT_TO_FLOAT3_I4).u(a, b, c, d).comp(k));
669 }
670 for (uint32_t k = 0; k < 4; k++) {
671 add("bn_hash_pcg4d_i", true, B(OP_PCG4D).u(a, b, c, d).comp(k));
672 add("bn_hash_int_to_float4", true, B(OP_INT_TO_FLOAT4).u(a, b, c, d).comp(k));
673 }
674 }
675
676 // The float-keyed families. The bit cast is the interesting part: C++ needs
677 // memcpy where GLSL has floatBitsToUint.
678 for (int i = 0; i < NF; i++) {
679 const float x = KF[i], y = KF[(i + 3) % NF], z = KF[(i + 5) % NF], w = KF[(i + 9) % NF];
680 for (uint32_t d = 1; d <= 4; d++) {
681 add("bn_hash_float", true, B(OP_HASH_FLOAT).dim(d).f(x, y, z, w));
682 add("bn_hff", true, B(OP_HFF).dim(d).f(x, y, z, w));
683 for (uint32_t k = 0; k < 3; k++) {
684 add("bn_hff3", true, B(OP_HFF3).dim(d).comp(k).f(x, y, z, w));
685 }
686 if (d >= 2) {
687 for (uint32_t k = 0; k < 2; k++) {
688 add("bn_hff2", true, B(OP_HFF2).dim(d).comp(k).f(x, y, z, w));
689 }
690 }
691 }
692 for (uint32_t k = 0; k < 4; k++) {
693 add("bn_hff4", true, B(OP_HFF4).comp(k).f(x, y, z, w));
694 }
695 }
696
697 // --- NEAR: perlin.
698 const float PC[][4] = {
699 { 0.0f, 0.0f, 0.0f, 0.0f },
700 { 0.5f, 1.5f, 2.5f, 3.5f },
701 { -7.25f, 0.5f, 13.75f, -2.5f },
702 { 123.456f, -78.9f, 0.001f, 44.0f },
703 // past the 100000 wrap and past the million where the nudge kicks in
704 { 123456.75f, -234567.5f, 1000001.5f, -1000000.5f },
705 };
706 const int NP = (int)(sizeof(PC) / sizeof(PC[0]));
707 // detail deliberately FRACTIONAL in most rows: rmd != 0 takes the
708 // extra-octave branch that four of the five fractal types have.
709 const float FP[][5] = {
710 { 3.0f, 0.5f, 2.0f, 0.0f, 1.0f },
711 { 2.5f, 0.5f, 2.0f, 0.7f, 1.0f },
712 { 5.25f, 0.75f, 2.5f, 0.3f, 1.5f },
713 { 0.0f, 0.5f, 2.0f, 1.0f, 2.0f },
714 { 7.5f, 0.25f, 1.75f, 0.5f, 0.5f },
715 };
716 const int NFP = (int)(sizeof(FP) / sizeof(FP[0]));
717
718 for (int i = 0; i < NP; i++) {
719 const float x = PC[i][0], y = PC[i][1], z = PC[i][2], w = PC[i][3];
720 for (uint32_t d = 1; d <= 4; d++) {
721 add("bp_perlin_noise", false, B(OP_PERLIN_NOISE).dim(d).f(x, y, z, w));
722 add("bp_perlin_signed", false, B(OP_PERLIN_SIGNED).dim(d).f(x, y, z, w));
723 }
724 for (int f = 0; f < NFP; f++) {
725 const float dt = FP[f][0], rg = FP[f][1], lc = FP[f][2];
726 const float of = FP[f][3], gn = FP[f][4];
727 // Every fractal type and every dimension: twenty functions stamped
728 // from one macro body, and a wrong suffix would make one dimension
729 // quietly evaluate another. The per-dimension scale constants --
730 // 0.2500, 0.6616, 0.9820, 0.8344 -- make that show up as a scale
731 // error rather than as noise.
732 for (int t = 0; t < 5; t++) {
733 for (int n = 0; n < 2; n++) {
734 const bool nb = (n != 0);
735 for (uint32_t d = 1; d <= 4; d++) {
736 char nm[64];
737 snprintf(nm, sizeof(nm), "bp_perlin_select_%u.t%d", d, t);
738 add(nm, false, B(OP_PERLIN_SELECT).dim(d).type(t).norm(nb)
739 .f(x, y, z, w).frac(dt, rg, lc, of, gn, 0.0f));
740 }
741 }
742 }
743 for (uint32_t d = 1; d <= 4; d++) {
744 char nm[64];
745 snprintf(nm, sizeof(nm), "bp_perlin_fractal_distorted_%u", d);
746 add(nm, false, B(OP_PERLIN_DISTORTED).dim(d).type(1).norm(true)
747 .f(x, y, z, w).frac(dt, rg, lc, of, gn, 0.75f));
748 for (uint32_t k = 0; k < 3; k++) {
749 snprintf(nm, sizeof(nm), "bp_perlin_float3(d%u)", d);
750 add(nm, false, B(OP_PERLIN_FLOAT3).dim(d).type(1).norm(true).comp(k)
751 .f(x, y, z, w).frac(dt, rg, lc, of, gn, 0.75f));
752 }
753 }
754 }
755 }
756}
757
758static long peak_rss_kb() {
759 std::ifstream f("/proc/self/status");
760 std::string line;
761 while (std::getline(f, line)) {
762 if (line.compare(0, 6, "VmHWM:") == 0) {
763 return atol(line.c_str() + 6);
764 }
765 }
766 return -1;
767}
768
769int main(int argc, char **argv) {
770 cap_own_memory();
771
772 std::vector<std::pair<std::string, std::string> > subjects;
773 if (argc > 2) {
774 for (int i = 1; i + 1 < argc; i += 2) {
775 subjects.push_back(std::make_pair(std::string(argv[i]), std::string(argv[i + 1])));
776 }
777 } else {
778 for (int i = 0; i < SUBJECT_N; i++) {
779 subjects.push_back(std::make_pair(std::string(SUBJECT_FILES[i]),
780 std::string(SUBJECT_MARKS[i])));
781 }
782 }
783 std::string lib, subject_desc;
784 for (size_t i = 0; i < subjects.size(); i++) {
785 const std::string &file = subjects[i].first;
786 const bool raw_inc = file.size() > 4 && file.compare(file.size() - 4, 4, ".inc") == 0;
787 std::string text = read_file(file);
788 lib += raw_inc ? text : extract_raw_string(text, subjects[i].second);
789 lib += "\n";
790 subject_desc += (i ? "\n " : "") + file;
791 }
792 if (lib.find("bn_hash_bit_final") == std::string::npos ||
793 lib.find("bp_perlin_signed") == std::string::npos) {
794 fprintf(stderr, "FATAL: subject text is missing bn_hash_bit_final or bp_perlin_signed"
795 " -- wrong subject?\n");
796 return 1;
797 }
798
799 build_cases();
800 if (g_cases.empty()) { fprintf(stderr, "FATAL: no cases\n"); return 1; }
801
802 int n_exact = 0;
803 for (size_t i = 0; i < g_cases.size(); i++) {
804 if (g_cases[i].exact) n_exact++;
805 }
806
807 // The opcode names come from the C++ enum, so the two dispatchers cannot
808 // drift by editing one of them.
809 std::string defs = "#version 450\n";
810 {
811 char b[128];
812 snprintf(b, sizeof(b), "#define STRIDE_W %uu\n#define CASE_COUNT %uu\n",
813 STRIDE, (unsigned)g_cases.size());
814 defs += b;
815 for (size_t i = 0; i < sizeof(OP_NAMES) / sizeof(OP_NAMES[0]); i++) {
816 snprintf(b, sizeof(b), "#define %s %uu\n", OP_NAMES[i].name, (unsigned)OP_NAMES[i].op);
817 defs += b;
818 }
819 }
820 const std::string shader = defs + lib + DISPATCH_GLSL;
821
822 Ctx c{};
823 init_vulkan(c);
824 printf("device : %s\n", c.devname);
825 printf("subject: %s (%zu bytes of GLSL, extracted not retyped)\n",
826 subject_desc.c_str(), lib.size());
827 printf("cases : %d EXACT (bit patterns, no epsilon), %d NEAR (rel %.0e)\n",
828 n_exact, (int)g_cases.size() - n_exact, TOL_REL);
829 printf("shader : %zu bytes, and its size does NOT depend on the case count\n\n",
830 shader.size());
831
832 std::vector<uint32_t> spv = compile_glsl(shader);
833
834 // inputs
835 std::vector<uint32_t> inwords;
836 inwords.reserve(g_cases.size() * STRIDE);
837 for (size_t i = 0; i < g_cases.size(); i++) {
838 for (uint32_t k = 0; k < STRIDE; k++) {
839 inwords.push_back(g_cases[i].w[k]);
840 }
841 }
842
843 VkBuffer buf, inbuf;
844 VkDeviceMemory mem, inmem;
845 void *mapped = nullptr;
846 void *inmapped = nullptr;
847 const VkDeviceSize out_bytes = sizeof(uint32_t) * g_cases.size();
848 const VkDeviceSize in_bytes = sizeof(uint32_t) * inwords.size();
849 {
850 VkBufferCreateInfo bi{VK_STRUCTURE_TYPE_BUFFER_CREATE_INFO};
851 bi.size = out_bytes;
852 bi.usage = VK_BUFFER_USAGE_STORAGE_BUFFER_BIT;
853 VK(vkCreateBuffer(c.dev, &bi, nullptr, &buf));
854 VkMemoryRequirements mr;
855 vkGetBufferMemoryRequirements(c.dev, buf, &mr);
856 VkMemoryAllocateInfo ai{VK_STRUCTURE_TYPE_MEMORY_ALLOCATE_INFO};
857 ai.allocationSize = mr.size;
858 ai.memoryTypeIndex = find_mem(c, mr.memoryTypeBits,
859 VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT | VK_MEMORY_PROPERTY_HOST_COHERENT_BIT);
860 VK(vkAllocateMemory(c.dev, &ai, nullptr, &mem));
861 VK(vkBindBufferMemory(c.dev, buf, mem, 0));
862 VK(vkMapMemory(c.dev, mem, 0, VK_WHOLE_SIZE, 0, &mapped));
863 memset(mapped, 0, out_bytes);
864
865 bi.size = in_bytes;
866 VK(vkCreateBuffer(c.dev, &bi, nullptr, &inbuf));
867 vkGetBufferMemoryRequirements(c.dev, inbuf, &mr);
868 ai.allocationSize = mr.size;
869 ai.memoryTypeIndex = find_mem(c, mr.memoryTypeBits,
870 VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT | VK_MEMORY_PROPERTY_HOST_COHERENT_BIT);
871 VK(vkAllocateMemory(c.dev, &ai, nullptr, &inmem));
872 VK(vkBindBufferMemory(c.dev, inbuf, inmem, 0));
873 VK(vkMapMemory(c.dev, inmem, 0, VK_WHOLE_SIZE, 0, &inmapped));
874 memcpy(inmapped, inwords.data(), in_bytes);
875 }
876
877 VkDescriptorSetLayoutBinding bind[2]{};
878 for (int i = 0; i < 2; i++) {
879 bind[i].binding = (uint32_t)i;
880 bind[i].descriptorType = VK_DESCRIPTOR_TYPE_STORAGE_BUFFER;
881 bind[i].descriptorCount = 1;
882 bind[i].stageFlags = VK_SHADER_STAGE_COMPUTE_BIT;
883 }
884 VkDescriptorSetLayoutCreateInfo dli{VK_STRUCTURE_TYPE_DESCRIPTOR_SET_LAYOUT_CREATE_INFO};
885 dli.bindingCount = 2; dli.pBindings = bind;
886 VkDescriptorSetLayout dsl;
887 VK(vkCreateDescriptorSetLayout(c.dev, &dli, nullptr, &dsl));
888
889 VkPipelineLayoutCreateInfo pli{VK_STRUCTURE_TYPE_PIPELINE_LAYOUT_CREATE_INFO};
890 pli.setLayoutCount = 1; pli.pSetLayouts = &dsl;
891 VkPipelineLayout playout;
892 VK(vkCreatePipelineLayout(c.dev, &pli, nullptr, &playout));
893
894 VkShaderModuleCreateInfo smi{VK_STRUCTURE_TYPE_SHADER_MODULE_CREATE_INFO};
895 smi.codeSize = spv.size() * 4; smi.pCode = spv.data();
896 VkShaderModule mod;
897 VK(vkCreateShaderModule(c.dev, &smi, nullptr, &mod));
898
899 VkPipelineShaderStageCreateInfo st{VK_STRUCTURE_TYPE_PIPELINE_SHADER_STAGE_CREATE_INFO};
900 st.stage = VK_SHADER_STAGE_COMPUTE_BIT; st.module = mod; st.pName = "main";
901 VkComputePipelineCreateInfo cpi{VK_STRUCTURE_TYPE_COMPUTE_PIPELINE_CREATE_INFO};
902 cpi.stage = st; cpi.layout = playout;
903 VkPipeline pipe;
904 VK(vkCreateComputePipelines(c.dev, VK_NULL_HANDLE, 1, &cpi, nullptr, &pipe));
905
906 VkDescriptorPoolSize ps{VK_DESCRIPTOR_TYPE_STORAGE_BUFFER, 2};
907 VkDescriptorPoolCreateInfo dpi{VK_STRUCTURE_TYPE_DESCRIPTOR_POOL_CREATE_INFO};
908 dpi.maxSets = 1; dpi.poolSizeCount = 1; dpi.pPoolSizes = &ps;
909 VkDescriptorPool dpool;
910 VK(vkCreateDescriptorPool(c.dev, &dpi, nullptr, &dpool));
911 VkDescriptorSetAllocateInfo dai{VK_STRUCTURE_TYPE_DESCRIPTOR_SET_ALLOCATE_INFO};
912 dai.descriptorPool = dpool; dai.descriptorSetCount = 1; dai.pSetLayouts = &dsl;
913 VkDescriptorSet dset;
914 VK(vkAllocateDescriptorSets(c.dev, &dai, &dset));
915 VkDescriptorBufferInfo dbi{buf, 0, VK_WHOLE_SIZE};
916 VkDescriptorBufferInfo dbi_in{inbuf, 0, VK_WHOLE_SIZE};
917 VkWriteDescriptorSet w[2]{};
918 for (int i = 0; i < 2; i++) {
919 w[i].sType = VK_STRUCTURE_TYPE_WRITE_DESCRIPTOR_SET;
920 w[i].dstSet = dset; w[i].dstBinding = (uint32_t)i; w[i].descriptorCount = 1;
921 w[i].descriptorType = VK_DESCRIPTOR_TYPE_STORAGE_BUFFER;
922 w[i].pBufferInfo = (i == 0) ? &dbi : &dbi_in;
923 }
924 vkUpdateDescriptorSets(c.dev, 2, w, 0, nullptr);
925
926 VkCommandBufferAllocateInfo cbi{VK_STRUCTURE_TYPE_COMMAND_BUFFER_ALLOCATE_INFO};
927 cbi.commandPool = c.cmdpool; cbi.level = VK_COMMAND_BUFFER_LEVEL_PRIMARY; cbi.commandBufferCount = 1;
928 VkCommandBuffer cmd;
929 VK(vkAllocateCommandBuffers(c.dev, &cbi, &cmd));
930 VkCommandBufferBeginInfo bbi{VK_STRUCTURE_TYPE_COMMAND_BUFFER_BEGIN_INFO};
931 bbi.flags = VK_COMMAND_BUFFER_USAGE_ONE_TIME_SUBMIT_BIT;
932 VK(vkBeginCommandBuffer(cmd, &bbi));
933 vkCmdBindPipeline(cmd, VK_PIPELINE_BIND_POINT_COMPUTE, pipe);
934 vkCmdBindDescriptorSets(cmd, VK_PIPELINE_BIND_POINT_COMPUTE, playout, 0, 1, &dset, 0, nullptr);
935 vkCmdDispatch(cmd, ((uint32_t)g_cases.size() + 63u) / 64u, 1, 1);
936 VK(vkEndCommandBuffer(cmd));
937 VkSubmitInfo si{VK_STRUCTURE_TYPE_SUBMIT_INFO};
938 si.commandBufferCount = 1; si.pCommandBuffers = &cmd;
939 VK(vkQueueSubmit(c.queue, 1, &si, VK_NULL_HANDLE));
940 VK(vkQueueWaitIdle(c.queue));
941
942 const uint32_t *got = (const uint32_t *)mapped;
943 int bad = 0, bad_exact = 0, n_bitident = 0;
944 double worst_rel = 0.0;
945 std::string worst_name;
946 for (size_t i = 0; i < g_cases.size(); i++) {
947 const Rec &k = g_cases[i];
948 bool ok;
949 if (k.exact) {
950 ok = (got[i] == k.cpu);
951 if (!ok) bad_exact++;
952 } else {
953 const double a = float_of(k.cpu), b = float_of(got[i]);
954 const double diff = fabs(a - b);
955 const double scale = fabs(a) > 1.0 ? fabs(a) : 1.0;
956 const double rel = diff / scale;
957 if (got[i] == k.cpu) n_bitident++;
958 if (rel > worst_rel) { worst_rel = rel; worst_name = k.name; }
959 ok = (diff <= TOL_ABS) || (rel <= TOL_REL);
960 }
961 if (!ok) {
962 bad++;
963 if (bad <= 20) {
964 printf(" FAIL %-30s dim=%u type=%u comp=%u cpu=%.9g gpu=%.9g\n",
965 k.name.c_str(), k.w[W_DIM], k.w[W_TYPE], k.w[W_COMP],
966 float_of(k.cpu), float_of(got[i]));
967 }
968 }
969 }
970 const int n_near = (int)g_cases.size() - n_exact;
971 printf("\n%zu/%zu cases agree (%d exact, %d of those failing)\n",
972 g_cases.size() - (size_t)bad, g_cases.size(), n_exact, bad_exact);
973 if (n_near > 0) {
974 printf("NEAR: %d/%d bit-identical anyway; worst relative error %.3g (%s)\n",
975 n_bitident, n_near, worst_rel, worst_name.c_str());
976 }
977 printf("peak RSS: %.1f MiB\n", peak_rss_kb() / 1024.0);
978 return bad ? 1 : 0;
979}