1 /** 2 Inochi2D Vector SIMD Helpers 3 4 Copyright: 5 Copyright © 2020-2026, Inochi2D Project 6 7 License: 8 $(LINK2 https://github.com/Inochi2D/inochi2d/blob/main/LICENSE, BSD 2-clause License) 9 10 Authors: 11 Luna Nielsen 12 */ 13 module inochi2d.core.math.simd.vector; 14 import inochi2d.core.math.simd; 15 import numem.core.math; 16 import numath; 17 import inteli; 18 19 /** 20 Calculates the bounding box of a mesh, for larger meshes SIMD 21 is used to optimize this operation. 22 23 Params: 24 mesh = The points of the mesh. 25 */ 26 rect simd_calcbounds(vec2[] mesh) @nogc nothrow { 27 28 // SIMD implementation will compare 2 vertices at the same time. 29 // Then do a final pass on the result. 30 __m128i m_off = __m128i([0, 1, 2, 3]); 31 __m128i m_offu = __m128i([0, 1, 0, 1]); 32 __m128 m_min = __m128([float.max, float.max, float.max, float.max]); 33 __m128 m_max = __m128([-float.min_normal, -float.min_normal, -float.min_normal, -float.min_normal]); 34 for (size_t i = 0; i < mesh.length; i += 2) { 35 36 // NOTE: In the case of an unaligned read, we use m_offu which just reads 37 // the same vertex twice. 38 __m128 m1 = _mm_i32gather_ps!(4)(mesh[i].ptr, i + 2 > mesh.length ? m_offu : m_off); 39 m_min = _mm_min_ps(m_min, m1); 40 m_max = _mm_max_ps(m_max, m1); 41 } 42 43 // Unpack and construct rectangle. 44 vec2 v_min = min(vec2(m_min[0], m_min[1]), vec2(m_min[2], m_min[3])); 45 vec2 v_max = min(vec2(m_max[0], m_max[1]), vec2(m_max[2], m_max[3])); 46 return rect( 47 v_min.x, 48 v_min.y, 49 v_max.x - v_min.x, 50 v_max.y - v_min.y, 51 ); 52 } 53 54 /** 55 Multiplies all of the vertices in a mesh with a given matrix. 56 For larger meshes this operation is done with SIMD. 57 58 Params: 59 mesh = The mesh to apply the transformation of the matrix to. 60 matrix = The matrix to apply. 61 */ 62 void simd_mul(ref vec2[] mesh, mat4 matrix) @nogc nothrow { 63 64 // NOTE: SSE version of the algorithm. 65 // This algorithm loads 128 bits of mesh data at a time, then deforms it. 66 // Value is stored unaligned to memory. 67 // 68 // TODO: Add aligned version? 69 static if (!SSESizedVectorsAreEmulated) { 70 71 // Load matrix into SIMD variables. 72 __m128 r0 = _mm_loadu_ps(&matrix.matrix[0][0]); 73 __m128 r1 = _mm_loadu_ps(&matrix.matrix[1][0]); 74 75 // SIMD version 76 size_t i = 0; 77 for (; i < nu_aligndown(mesh.length, 2); i += 2) { 78 79 // Load vectors into SIMD variables. 80 __m128 xy01 = _mm_loadl_pi(IN_SIMD_IDENTITY, cast(const(__m64)*)mesh[i].ptr); 81 __m128 zw01 = _mm_loadl_pi(IN_SIMD_IDENTITY, cast(const(__m64)*)mesh[i + 1].ptr); 82 83 // Perform matrix multiplication 84 __m128 x = _mm_mul_ps(xy01, r0); 85 __m128 y = _mm_mul_ps(xy01, r1); 86 __m128 z = _mm_mul_ps(zw01, r0); 87 __m128 w = _mm_mul_ps(zw01, r1); 88 __m128 xy = _mm_hadd_ps(x, y); 89 __m128 zw = _mm_hadd_ps(z, w); 90 __m128 xyzw = _mm_hadd_ps(xy, zw); 91 92 // Store 2 multiplied elements at once to mesh. 93 _mm_storeu_ps(cast(float*)mesh[i].ptr, xyzw); 94 } 95 96 // Tail iteration to finalize the multiplication 97 if (i < mesh.length) { 98 __m128 xy01 = _mm_loadl_pi(IN_SIMD_IDENTITY, cast(const(__m64)*)mesh[i].ptr); 99 __m128 x = _mm_mul_ps(xy01, r0); 100 __m128 y = _mm_mul_ps(xy01, r1); 101 __m128 xy = _mm_hadd_ps(_mm_hadd_ps(x, y), _mm_hadd_ps(x, y)); 102 _mm_storel_pi(cast(__m64*)mesh[i].ptr, xy); 103 } 104 } else { 105 106 // Non-SIMD version 107 foreach (ref vertex; mesh) { 108 vertex = (vec4(vertex.x, vertex.y, 0, 1) * matrix).xy; 109 } 110 } 111 } 112 113 @("simd_mul") 114 unittest { 115 mat4 testMatrix = mat4.translation(1.0, 0.0, 0.0); 116 vec2[] testArray = new vec2[10_001]; 117 testArray[] = vec2(1.0, 1.0); 118 119 simd_mul(testArray, testMatrix); 120 foreach (i, value; testArray) { 121 assert(value == vec2(2.0, 1.0)); 122 } 123 } 124 125 /** 126 Offsets all of the coordinates in the given mesh with the given offset. 127 For larger meshes, the offset is done with SIMD. 128 129 Params: 130 mesh = The mesh to offset. 131 offset = The offset to perform 132 */ 133 void simd_offset(ref vec2[] mesh, vec2 offset) @nogc nothrow { 134 static if (!SSESizedVectorsAreEmulated) { 135 136 // Offset loaded from variable. 137 __m128 m_offset = _mm_set_ps(offset.y, offset.x, offset.y, offset.x); 138 139 // SIMD version 140 size_t i = 0; 141 for (; i < nu_aligndown(mesh.length, 2); i += 2) { 142 _mm_storeu_ps( 143 cast(float*)mesh[i].ptr, 144 _mm_add_ps( 145 _mm_loadu_ps(cast(float*)mesh[i].ptr), 146 m_offset 147 ) 148 ); 149 } 150 151 // Tail iteration to finalize the offset 152 if (i < mesh.length) { 153 _mm_storel_pi( 154 cast(__m64*)mesh[i].ptr, 155 _mm_add_ps( 156 _mm_loadl_pi( 157 IN_SIMD_IDENTITY, 158 cast(const(__m64)*)&mesh[i] 159 ), 160 m_offset 161 ) 162 ); 163 } 164 } else { 165 166 // Non-SIMD version 167 foreach (i; 0 .. mesh.length) { 168 mesh[i] += offset; 169 } 170 } 171 } 172 173 @("simd_offset") 174 unittest { 175 vec2[] array1 = new vec2[10_001]; 176 array1[] = vec2(0); 177 178 simd_offset(array1, vec2(1, 1)); 179 foreach (i, value; array1) { 180 assert(value == vec2(1.0, 1.0)); 181 } 182 } 183 184 /** 185 Multiplies all vertices in a given mesh with the given weights. 186 187 Params: 188 mesh = The mesh to scale based on weight. 189 weights = The weights to scale by. 190 */ 191 void simd_mul_weight(ref vec2[] mesh, ref float[] weights) @nogc nothrow { 192 size_t w_length = nu_min(mesh.length, weights.length); 193 194 // NOTE: SSE version of the algorithm. 195 // This algorithm loads 128 bits of mesh data at a time, then deforms it. 196 // Value is stored unaligned to memory. 197 // 198 // TODO: Add aligned version? 199 __gshared const __m128i WEIGHT_OFFSETS = __m128i([0, 0, 1, 1]); 200 static if (!SSESizedVectorsAreEmulated) { 201 202 // SIMD version 203 size_t i = 0; 204 for (; i < nu_aligndown(w_length, 2); i += 2) { 205 206 // Load weights and vector 207 __m128 w0011 = _mm_i32gather_ps!(4)(cast(const(float)*)&weights[i], WEIGHT_OFFSETS); 208 __m128 xyzw = _mm_load_ps(cast(const(float)*)&mesh[i]); 209 210 // Perform matrix multiplication and 211 // Store 2 multiplied elements at once to mesh. 212 __m128 weighted = _mm_mul_ps(xyzw, w0011); 213 _mm_storeu_ps(cast(float*)mesh[i].ptr, weighted); 214 215 } 216 217 // Tail iteration to finalize the multiplication 218 if (i < w_length) { 219 mesh[i] = mesh[i] * weights[i]; 220 } 221 } else { 222 223 // Non-SIMD version 224 foreach (i; 0 .. w_length) { 225 mesh[i] = mesh[i] * weights[i]; 226 } 227 } 228 } 229 230 @("simd_mul_weight") 231 unittest { 232 vec2[] array1 = new vec2[10_001]; 233 array1[] = vec2(0.5); 234 235 float[] weights = new float[10_001]; 236 weights[] = 0.5; 237 238 simd_mul_weight(array1, weights); 239 foreach (i, value; array1) { 240 assert(value == vec2(0.25, 0.25)); 241 } 242 }