| … | … | @@ -58,6 +58,14 @@ glsl_inc_gen = [ |
| 58 | 58 | "src/shared/blender_hash.inc", |
| 59 | 59 | "python3 tools/gen_glsl_inc.py $SOURCE $TARGET BLENDER_HASH_GLSL", |
| 60 | 60 | ), |
| 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 | ), | |
| 61 | 69 | ] |
| 62 | 70 | |
| 63 | 71 | sources = ( |
| … | … | @@ -27,6 +27,7 @@ |
| 27 | 27 | // site, which is what blender_noise.h now is. |
| 28 | 28 | // ========================================================================= |
| 29 | 29 | |
| 30 | #include <cmath> | |
| 30 | 31 | #include <cstdint> |
| 31 | 32 | #include <cstring> |
| 32 | 33 | |
| … | … | @@ -99,8 +100,56 @@ inline uint floatBitsToUint(float f) { |
| 99 | 100 | return i; |
| 100 | 101 | } |
| 101 | 102 | |
| 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 | ||
| 114 | inline vec2 operator+(vec2 a, vec2 b) { return vec2(a.x + b.x, a.y + b.y); } | |
| 115 | inline vec2 operator-(vec2 a, vec2 b) { return vec2(a.x - b.x, a.y - b.y); } | |
| 116 | inline vec2 operator*(vec2 a, vec2 b) { return vec2(a.x * b.x, a.y * b.y); } | |
| 117 | inline vec2 operator*(vec2 a, float s) { return vec2(a.x * s, a.y * s); } | |
| 118 | inline vec2 operator*(float s, vec2 a) { return vec2(s * a.x, s * a.y); } | |
| 119 | inline vec2 operator+(vec2 a, float s) { return vec2(a.x + s, a.y + s); } | |
| 120 | inline vec2 operator-(vec2 a) { return vec2(-a.x, -a.y); } | |
| 121 | ||
| 122 | inline vec3 operator+(vec3 a, vec3 b) { return vec3(a.x + b.x, a.y + b.y, a.z + b.z); } | |
| 123 | inline vec3 operator-(vec3 a, vec3 b) { return vec3(a.x - b.x, a.y - b.y, a.z - b.z); } | |
| 124 | inline vec3 operator*(vec3 a, vec3 b) { return vec3(a.x * b.x, a.y * b.y, a.z * b.z); } | |
| 125 | inline vec3 operator*(vec3 a, float s) { return vec3(a.x * s, a.y * s, a.z * s); } | |
| 126 | inline vec3 operator*(float s, vec3 a) { return vec3(s * a.x, s * a.y, s * a.z); } | |
| 127 | inline vec3 operator+(vec3 a, float s) { return vec3(a.x + s, a.y + s, a.z + s); } | |
| 128 | inline vec3 operator-(vec3 a) { return vec3(-a.x, -a.y, -a.z); } | |
| 129 | ||
| 130 | inline vec4 operator+(vec4 a, vec4 b) { return vec4(a.x + b.x, a.y + b.y, a.z + b.z, a.w + b.w); } | |
| 131 | inline vec4 operator-(vec4 a, vec4 b) { return vec4(a.x - b.x, a.y - b.y, a.z - b.z, a.w - b.w); } | |
| 132 | inline vec4 operator*(vec4 a, vec4 b) { return vec4(a.x * b.x, a.y * b.y, a.z * b.z, a.w * b.w); } | |
| 133 | inline vec4 operator*(vec4 a, float s) { return vec4(a.x * s, a.y * s, a.z * s, a.w * s); } | |
| 134 | inline vec4 operator*(float s, vec4 a) { return vec4(s * a.x, s * a.y, s * a.z, s * a.w); } | |
| 135 | inline vec4 operator+(vec4 a, float s) { return vec4(a.x + s, a.y + s, a.z + s, a.w + s); } | |
| 136 | inline 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. | |
| 141 | inline float floor(float x) { return std::floor(x); } | |
| 142 | inline float trunc(float x) { return std::trunc(x); } | |
| 143 | inline float abs(float x) { return std::fabs(x); } | |
| 144 | inline float min(float a, float b) { return a < b ? a : b; } | |
| 145 | inline float max(float a, float b) { return a > b ? a : b; } | |
| 146 | inline 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. | |
| 103 | 151 | #include "blender_hash.inc" |
| 152 | #include "blender_perlin.inc" | |
| 104 | 153 | |
| 105 | 154 | } // namespace bnoisegl |
| 106 | 155 | } // namespace godot |
| … | … | @@ -1,32 +1,42 @@ |
| 1 | 1 | #ifndef PROC_NODES_BLENDER_PERLIN_H |
| 2 | 2 | #define PROC_NODES_BLENDER_PERLIN_H |
| 3 | 3 | |
| 4 | // BLENDER'S PERLIN NOISE, PORTED FROM BLENDER'S SOURCE. | |
| 4 | // BLENDER'S PERLIN NOISE — NOW AN ADAPTER, NOT AN IMPLEMENTATION. | |
| 5 | 5 | // |
| 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. | |
| 11 | 13 | // |
| 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. | |
| 17 | 19 | // |
| 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: | |
| 19 | 29 | // |
| 20 | 30 | // 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 | |
| 24 | 34 | // iq/Blender" in ProcNodes_Noised, where it did not belong. |
| 25 | 35 | // |
| 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. | |
| 30 | 40 | |
| 31 | 41 | #include "blender_noise.h" |
| 32 | 42 | |
| … | … | @@ -34,8 +44,6 @@ |
| 34 | 44 | #include <godot_cpp/variant/vector3.hpp> |
| 35 | 45 | #include <godot_cpp/variant/vector4.hpp> |
| 36 | 46 | |
| 37 | #include <cmath> | |
| 38 | ||
| 39 | 47 | namespace godot { |
| 40 | 48 | namespace bperlin { |
| 41 | 49 | |
| … | … | @@ -49,423 +57,118 @@ enum { |
| 49 | 57 | PERLIN_HETERO_TERRAIN = 4, |
| 50 | 58 | }; |
| 51 | 59 | |
| 52 | // Blender's mix: (1 - x) * v0 + x * v1. | |
| 53 | static inline float pmix(float v0, float v1, float x) { return (1.0f - x) * v0 + x * v1; } | |
| 54 | ||
| 55 | static 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 | ||
| 60 | static 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 | ||
| 69 | static 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 | ||
| 76 | static inline float fade(float t) { return t * t * t * (t * (t * 6.0f - 15.0f) + 10.0f); } | |
| 77 | ||
| 78 | static inline float negate_if(float value, uint32_t condition) { | |
| 79 | return (condition != 0u) ? -value : value; | |
| 80 | } | |
| 81 | ||
| 82 | static 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 | ||
| 88 | static 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 | ||
| 95 | static 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 | ||
| 103 | static 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 | ||
| 111 | static 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 | ||
| 117 | static 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 | ||
| 125 | static 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 | ||
| 137 | static 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 | ||
| 158 | static 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. | |
| 198 | static 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 | ||
| 204 | static 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 | ||
| 211 | static 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 | ||
| 221 | static 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. */ | |
| 60 | namespace detail { | |
| 61 | inline bnoisegl::vec2 gl(const Vector2 &v) { return bnoisegl::vec2(v.x, v.y); } | |
| 62 | inline bnoisegl::vec3 gl(const Vector3 &v) { return bnoisegl::vec3(v.x, v.y, v.z); } | |
| 63 | inline bnoisegl::vec4 gl(const Vector4 &v) { return bnoisegl::vec4(v.x, v.y, v.z, v.w); } | |
| 64 | inline Vector3 gd(const bnoisegl::vec3 &v) { return Vector3(v.x, v.y, v.z); } | |
| 65 | } // namespace detail | |
| 233 | 66 | |
| 234 | template <typename T> | |
| 235 | static 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 --- | |
| 258 | 68 | |
| 259 | 69 | template <typename T> |
| 260 | static 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 | } | |
| 70 | float perlin_fbm(T p, float detail_v, float roughness, float lacunarity, bool normalize); | |
| 270 | 71 | |
| 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; | |
| 72 | template <> | |
| 73 | inline 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); | |
| 277 | 75 | } |
| 278 | ||
| 279 | template <typename T> | |
| 280 | static 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; | |
| 76 | template <> | |
| 77 | inline 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); | |
| 302 | 79 | } |
| 303 | ||
| 304 | template <typename T> | |
| 305 | static 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; | |
| 80 | template <> | |
| 81 | inline 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); | |
| 329 | 83 | } |
| 330 | ||
| 331 | template <typename T> | |
| 332 | static 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; | |
| 84 | template <> | |
| 85 | inline 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); | |
| 352 | 87 | } |
| 353 | 88 | |
| 354 | 89 | template <typename T> |
| 355 | static 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. | |
| 374 | static inline float random_float_offset(float seed) { | |
| 375 | return 100.0f + hash_float_to_float(seed) * 100.0f; | |
| 376 | } | |
| 377 | static 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 | } | |
| 381 | static 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 | } | |
| 386 | static 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 | } | |
| 90 | float perlin_select(T p, float detail_v, float roughness, float lacunarity, float offset, | |
| 91 | float gain, int type, bool normalize); | |
| 392 | 92 | |
| 393 | static inline float perlin_distortion(float position, float strength) { | |
| 394 | return perlin_signed(position + random_float_offset(0.0f)) * strength; | |
| 93 | template <> | |
| 94 | inline 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); | |
| 395 | 97 | } |
| 396 | static 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); | |
| 98 | template <> | |
| 99 | inline 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); | |
| 399 | 102 | } |
| 400 | static 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); | |
| 103 | template <> | |
| 104 | inline 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); | |
| 404 | 107 | } |
| 405 | static 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); | |
| 108 | template <> | |
| 109 | inline 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); | |
| 410 | 112 | } |
| 411 | 113 | |
| 412 | 114 | template <typename T> |
| 413 | static 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. | |
| 422 | static 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 | ||
| 434 | static 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 | ||
| 446 | static 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 | ||
| 458 | static 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 | } | |
| 115 | float perlin_fractal_distorted(T p, float detail_v, float roughness, float lacunarity, | |
| 116 | float offset, float gain, float distortion, int type, bool normalize); | |
| 117 | ||
| 118 | template <> | |
| 119 | inline 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 | } | |
| 123 | template <> | |
| 124 | inline 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 | } | |
| 128 | template <> | |
| 129 | inline 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 | } | |
| 133 | template <> | |
| 134 | inline 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. | |
| 141 | inline 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 | } | |
| 145 | inline 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 | } | |
| 150 | inline 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 | } | |
| 155 | inline 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 | ||
| 163 | inline float perlin_noise(float p) { return bnoisegl::bp_perlin_noise(p); } | |
| 164 | inline float perlin_noise(const Vector2 &p) { return bnoisegl::bp_perlin_noise(detail::gl(p)); } | |
| 165 | inline float perlin_noise(const Vector3 &p) { return bnoisegl::bp_perlin_noise(detail::gl(p)); } | |
| 166 | inline float perlin_noise(const Vector4 &p) { return bnoisegl::bp_perlin_noise(detail::gl(p)); } | |
| 167 | ||
| 168 | inline float perlin_signed(float p) { return bnoisegl::bp_perlin_signed(p); } | |
| 169 | inline float perlin_signed(const Vector2 &p) { return bnoisegl::bp_perlin_signed(detail::gl(p)); } | |
| 170 | inline float perlin_signed(const Vector3 &p) { return bnoisegl::bp_perlin_signed(detail::gl(p)); } | |
| 171 | inline float perlin_signed(const Vector4 &p) { return bnoisegl::bp_perlin_signed(detail::gl(p)); } | |
| 469 | 172 | |
| 470 | 173 | } // namespace bperlin |
| 471 | 174 | } // namespace godot |
| … | … | @@ -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 | ||
| 65 | const int BP_MULTIFRACTAL = 0; | |
| 66 | const int BP_FBM = 1; | |
| 67 | const int BP_HYBRID_MULTIFRACTAL = 2; | |
| 68 | const int BP_RIDGED_MULTIFRACTAL = 3; | |
| 69 | const 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. | |
| 74 | BN_FN float bp_pmix(float v0, float v1, float x) { return (1.0f - x) * v0 + x * v1; } | |
| 75 | ||
| 76 | BN_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 | ||
| 81 | BN_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 | ||
| 90 | BN_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 | ||
| 97 | BN_FN float bp_fade(float t) { return t * t * t * (t * (t * 6.0f - 15.0f) + 10.0f); } | |
| 98 | ||
| 99 | BN_FN float bp_negate_if(float value, uint condition) { | |
| 100 | return (condition != 0u) ? -value : value; | |
| 101 | } | |
| 102 | ||
| 103 | BN_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 | ||
| 109 | BN_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 | ||
| 116 | BN_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 | ||
| 124 | BN_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. | |
| 133 | BN_FN int bp_floor_int(float x) { return int(floor(x)); } | |
| 134 | BN_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. | |
| 138 | BN_FN float bp_fmod(float a, float b) { return a - b * trunc(a / b); } | |
| 139 | ||
| 140 | BN_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 | ||
| 148 | BN_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 | ||
| 161 | BN_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 | ||
| 184 | BN_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. | |
| 223 | BN_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 | ||
| 229 | BN_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 | ||
| 236 | BN_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 | ||
| 245 | BN_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. | |
| 257 | BN_FN float bp_random_float_offset(float seed) { | |
| 258 | return 100.0f + bn_hash_float_to_float(seed) * 100.0f; | |
| 259 | } | |
| 260 | BN_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 | } | |
| 264 | BN_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 | } | |
| 269 | BN_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 | ||
| 276 | BN_FN float bp_perlin_distortion(float position, float strength) { | |
| 277 | return bp_perlin_signed(position + bp_random_float_offset(0.0f)) * strength; | |
| 278 | } | |
| 279 | BN_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 | } | |
| 283 | BN_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 | } | |
| 288 | BN_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) \ | |
| 304 | BN_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 | \ | |
| 333 | BN_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 | \ | |
| 348 | BN_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 | \ | |
| 368 | BN_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 | \ | |
| 390 | BN_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 | \ | |
| 409 | BN_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 | \ | |
| 429 | BN_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 | ||
| 436 | BP_FRACTALS(float, 1) | |
| 437 | BP_FRACTALS(vec2, 2) | |
| 438 | BP_FRACTALS(vec3, 3) | |
| 439 | BP_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. | |
| 445 | BN_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 | ||
| 456 | BN_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 | ||
| 467 | BN_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 | ||
| 478 | BN_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 |
| … | … | @@ -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. | |
| 63 | static const char *SUBJECT_FILE = "src/gpu/blender_hash_glsl.gen.h"; | |
| 64 | static const char *SUBJECT_MARK = "BLENDER_HASH_GLSL"; | |
| 65 | ||
| 66 | static 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 | ||
| 72 | static 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. | |
| 83 | static 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 | ||
| 115 | static 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 | ||
| 137 | struct 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 | ||
| 148 | static 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 | ||
| 158 | static 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 | ||
| 228 | struct 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 | ||
| 234 | static uint32_t bits_of(float f) { | |
| 235 | uint32_t u; | |
| 236 | memcpy(&u, &f, sizeof(u)); | |
| 237 | return u; | |
| 238 | } | |
| 239 | static 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. | |
| 246 | static std::string F(float v) { | |
| 247 | char b[64]; | |
| 248 | snprintf(b, sizeof(b), "uintBitsToFloat(0x%08xu)", bits_of(v)); | |
| 249 | return b; | |
| 250 | } | |
| 251 | static std::string U(uint32_t v) { | |
| 252 | char b[32]; | |
| 253 | snprintf(b, sizeof(b), "%uu", v); | |
| 254 | return b; | |
| 255 | } | |
| 256 | static 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 | ||
| 265 | int 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 | } |
| … | … | @@ -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 | ||
| 89 | using 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. | |
| 93 | static const char *SUBJECT_FILES[] = { | |
| 94 | "src/gpu/blender_hash_glsl.gen.h", | |
| 95 | "src/gpu/blender_perlin_glsl.gen.h", | |
| 96 | }; | |
| 97 | static const char *SUBJECT_MARKS[] = { "BLENDER_HASH_GLSL", "BLENDER_PERLIN_GLSL" }; | |
| 98 | static const int SUBJECT_N = 2; | |
| 99 | ||
| 100 | static const double TOL_REL = 1e-5; | |
| 101 | static 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. | |
| 106 | static const rlim_t MEM_LIMIT_BYTES = (rlim_t)4 * 1024 * 1024 * 1024; | |
| 107 | ||
| 108 | static 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 | ||
| 129 | static 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 | ||
| 150 | static 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. | |
| 161 | static 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 | ||
| 188 | static 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 | ||
| 210 | struct 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 | ||
| 221 | static 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 | ||
| 231 | static 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 | ||
| 287 | static const uint32_t STRIDE = 16; | |
| 288 | ||
| 289 | enum 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. | |
| 312 | struct OpName { Op op; const char *name; }; | |
| 313 | static 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 | ||
| 331 | struct Rec { | |
| 332 | uint32_t w[STRIDE]; | |
| 333 | std::string name; | |
| 334 | bool exact; | |
| 335 | uint32_t cpu; | |
| 336 | }; | |
| 337 | ||
| 338 | static uint32_t bits_of(float f) { uint32_t u; memcpy(&u, &f, sizeof(u)); return u; } | |
| 339 | static 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. | |
| 342 | enum { 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 ----------------------------------------- | |
| 347 | static 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). ------------------------------ | |
| 447 | static const char *DISPATCH_GLSL = R"DISPATCH( | |
| 448 | layout(local_size_x = 64) in; | |
| 449 | layout(std430, binding = 0) buffer Out { uint v[]; } out_v; | |
| 450 | layout(std430, binding = 1) readonly buffer In { uint v[]; } in_v; | |
| 451 | ||
| 452 | uint gW(uint b, uint k) { return in_v.v[b + k]; } | |
| 453 | float gF(uint b, uint k) { return uintBitsToFloat(in_v.v[b + k]); } | |
| 454 | ||
| 455 | void 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 | ||
| 592 | struct 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 | ||
| 617 | static std::vector<Rec> g_cases; | |
| 618 | ||
| 619 | static 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 | ||
| 628 | static 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 | ||
| 758 | static 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 | ||
| 769 | int 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 | } |