1 /** 2 Inochi2D Mesh 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.mesh; 14 import inochi2d.core.math.simd; 15 import inochi2d.core.mesh; 16 import numem.core.math; 17 import numem.core.hooks; 18 import numath; 19 import inteli; 20 21 /** 22 Performs deformation on the delta mesh. 23 24 Params: 25 mesh = The mesh to deform. 26 deform = The mesh with the deformation deltas. 27 */ 28 void simd_deform(ref vec2[] mesh, vec2[] deform) @nogc nothrow { 29 size_t w_length = nu_min(mesh.length, deform.length); 30 static if (!AVXSizedVectorsAreEmulated) { 31 32 // NOTE: AVX version of the algorithm. 33 // This algorithm loads 256 bits of mesh data at a time, then deforms it. 34 // Value is stored unaligned to memory. 35 // 36 // TODO: Add aligned version? 37 size_t i = 0; 38 for (; i < nu_aligndown(w_length, 4); i += 4) { 39 40 // Get 4 values at the same from both the mesh and deform. 41 __m256 m_xyzwuvst = _mm256_loadu_ps(cast(float*)&mesh[i]); 42 __m256 d_xyzwuvst = _mm256_loadu_ps(cast(float*)&deform[i]); 43 44 // Add and store 2 vectors at the same time. 45 __m256 xyzwuvst = _mm256_add_ps(m_xyzwuvst, d_xyzwuvst); 46 _mm256_storeu_ps(cast(float*)&mesh[i], xyzwuvst); 47 } 48 49 // SSE for 2-3 remaining values. 50 if (i < nu_aligndown(w_length, 2)) { 51 52 // Get 4 values at the same from both the mesh and deform. 53 __m128 m_xyzw = _mm_loadu_ps(cast(float*)&mesh[i]); 54 __m128 d_xyzw = _mm_loadu_ps(cast(float*)&deform[i]); 55 56 // Add and store 2 vectors at the same time. 57 __m128 xyzw = _mm_add_ps(m_xyzw, d_xyzw); 58 _mm_storeu_ps(cast(float*)&mesh[i], xyzw); 59 60 i += 2; 61 } 62 63 // Tail iteration to finalize the broadcast 64 if (i < w_length) { 65 __m128 m_xy01 = _mm_loadl_pi(IN_SIMD_IDENTITY, cast(const(__m64)*)&mesh[i]); 66 __m128 d_xy01 = _mm_loadl_pi(IN_SIMD_IDENTITY, cast(const(__m64)*)&deform[i]); 67 __m128 xy01 = _mm_add_ps(m_xy01, d_xy01); 68 _mm_storel_pi(cast(__m64*)mesh[i].ptr, xy01); 69 } 70 } else static if (!SSESizedVectorsAreEmulated) { 71 72 // NOTE: SSE version of the algorithm. 73 // This algorithm loads 128 bits of mesh data at a time, then deforms it. 74 // Value is stored unaligned to memory. 75 // 76 // TODO: Add aligned version? 77 size_t i = 0; 78 for (; i < nu_aligndown(w_length, 2); i += 2) { 79 80 // Get 4 values at the same from both the mesh and deform. 81 __m128 m_xyzw = _mm_loadu_ps(cast(float*)&mesh[i]); 82 __m128 d_xyzw = _mm_loadu_ps(cast(float*)&deform[i]); 83 84 // Add and store 2 vectors at the same time. 85 __m128 xyzw = _mm_add_ps(m_xyzw, d_xyzw); 86 _mm_storeu_ps(cast(float*)&mesh[i], xyzw); 87 } 88 89 // Tail iteration to finalize the broadcast 90 if (i < w_length) { 91 __m128 m_xy01 = _mm_loadl_pi(IN_SIMD_IDENTITY, cast(const(__m64)*)&mesh[i]); 92 __m128 d_xy01 = _mm_loadl_pi(IN_SIMD_IDENTITY, cast(const(__m64)*)&deform[i]); 93 __m128 xy01 = _mm_add_ps(m_xy01, d_xy01); 94 _mm_storel_pi(cast(__m64*)&mesh[i], xy01); 95 } 96 } else { 97 98 // Non-SIMD version 99 foreach (i; 0 .. w_length) { 100 mesh[i] += deform[i]; 101 } 102 } 103 } 104 105 @("simd_deform") 106 unittest { 107 vec2[] array1 = new vec2[10_001]; 108 vec2[] array2 = new vec2[10_001]; 109 110 array1[] = vec2(1.0, 1.0); 111 array2[] = vec2(1.0, 0.0); 112 113 simd_deform(array1, array2); 114 foreach (value; array1) { 115 assert(value == vec2(2.0, 1.0)); 116 } 117 } 118 119 /** 120 Broadcasts vertex data from the given delta mesh into 121 the given VtxData mesh. 122 123 SIMD is used in the case of larger meshes. 124 125 Params: 126 mesh = The mesh to broadcast the deltas to. 127 delta = The deltas to broadcast to the mesh. 128 */ 129 void simd_broadcast_mesh(ref VtxData[] mesh, vec2[] delta) @nogc nothrow { 130 131 // Write length, in case there's a mismatch of sizes. 132 size_t w_length = nu_min(mesh.length, delta.length); 133 134 // NOTE: SSE version of the algorithm. 135 // Value is stored unaligned to memory. 136 // 137 // TODO: Add aligned version? 138 static if (!SSESizedVectorsAreEmulated) { 139 140 size_t i = 0; 141 for (; i < nu_aligndown(w_length, 2); i += 2) { 142 143 // Load vectors into SIMD variables, then uses SIMD store 144 // to write it to the XY components of the VtxDatas. 145 __m128 xyzw = _mm_loadu_ps(cast(float*)delta[i].ptr); 146 _mm_storel_pi(cast(__m64*)&mesh[i], xyzw); 147 _mm_storeh_pi(cast(__m64*)&mesh[i + 1], xyzw); 148 } 149 150 // Tail iteration to finalize the broadcast 151 if (i < w_length) { 152 mesh[i].vtx.data[0 .. 2] = delta[i].data[0 .. 2]; 153 } 154 } else { 155 156 // Non-SIMD version 157 foreach (i; 0 .. w_length) { 158 mesh[i].vtx.data[0 .. 2] = delta[i].data[0 .. 2]; 159 } 160 } 161 } 162 163 @("simd_broadcast_mesh") 164 unittest { 165 VtxData[] array1 = new VtxData[10_001]; 166 vec2[] array2 = new vec2[10_001]; 167 168 array1[] = VtxData(vtx_t(0), vec2(0)); 169 array2[] = vec2(1.0, 1.0); 170 171 simd_broadcast_mesh(array1, array2); 172 foreach (value; array1) { 173 assert(value.vtx.xy == vec2(1.0, 1.0)); 174 } 175 } 176 177 /** 178 Copies the data from the source buffer into the destination. 179 180 Params: 181 dst = Destination 182 src = Source 183 */ 184 void simd_meshcopy(T)(ref T[] dst, T[] src) @nogc nothrow { 185 nu_memcpy(dst.ptr, src.ptr, nu_min(dst.length, src.length) * T.sizeof); 186 } 187 188 /** 189 Adds the values in the $(D rhs) mesh 190 to the $(D lhs) mesh. 191 192 Params: 193 lhs = The left-hand side mesh. 194 rhs = The right-hand side mesh. 195 */ 196 void simd_add(T)(ref T[] lhs, T[] rhs) @nogc nothrow 197 if (is(T == VectorImpl!U, U...) && T.dimensions == 2) { 198 size_t w_length = nu_min(lhs.length, rhs.length); 199 200 // NOTE: SSE version of the algorithm. 201 // This algorithm loads 128 bits of mesh data at a time, then deforms it. 202 // Value is stored unaligned to memory. 203 // 204 // TODO: Add aligned version? 205 static if (!SSESizedVectorsAreEmulated) { 206 207 // SIMD version 208 size_t i = 0; 209 for (; i < nu_aligndown(w_length, 2); i += 2) { 210 __m128 xyzw = _mm_load_ps(cast(const(float)*)&lhs[i]); 211 __m128 abcd = _mm_load_ps(cast(const(float)*)&rhs[i]); 212 _mm_storeu_ps(cast(float*)&lhs[i], _mm_add_ps(xyzw, abcd)); 213 } 214 215 // Tail iteration to finalize the multiplication 216 if (i < w_length) { 217 lhs[i] = lhs[i] + rhs[i]; 218 } 219 } else { 220 221 // Non-SIMD version 222 foreach (i; 0 .. w_length) { 223 lhs[i] = lhs[i] + rhs[i]; 224 } 225 } 226 } 227 228 /** 229 Subtracts the values in the $(D rhs) mesh 230 from the $(D lhs) mesh. 231 232 Params: 233 lhs = The left-hand side mesh. 234 rhs = The right-hand side mesh. 235 */ 236 void simd_sub(T)(ref T[] lhs, T[] rhs) @nogc nothrow 237 if (is(T == VectorImpl!U, U...) && T.dimensions == 2) { 238 size_t w_length = nu_min(lhs.length, rhs.length); 239 240 // NOTE: SSE version of the algorithm. 241 // This algorithm loads 128 bits of mesh data at a time, then deforms it. 242 // Value is stored unaligned to memory. 243 // 244 // TODO: Add aligned version? 245 static if (!SSESizedVectorsAreEmulated) { 246 247 // SIMD version 248 size_t i = 0; 249 for (; i < nu_aligndown(w_length, 2); i += 2) { 250 __m128 xyzw = _mm_load_ps(cast(const(float)*)&lhs[i]); 251 __m128 abcd = _mm_load_ps(cast(const(float)*)&rhs[i]); 252 _mm_storeu_ps(cast(float*)&lhs[i], _mm_sub_ps(xyzw, abcd)); 253 } 254 255 // Tail iteration to finalize the multiplication 256 if (i < w_length) { 257 lhs[i] = lhs[i] - rhs[i]; 258 } 259 } else { 260 261 // Non-SIMD version 262 foreach (i; 0 .. w_length) { 263 lhs[i] = lhs[i] - rhs[i]; 264 } 265 } 266 } 267 268 /** 269 Multiplies the values in the $(D rhs) mesh 270 with the $(D lhs) mesh. 271 272 Params: 273 lhs = The left-hand side mesh. 274 rhs = The right-hand side mesh. 275 */ 276 void simd_mul(T)(ref T[] lhs, T[] rhs) @nogc nothrow 277 if (is(T == VectorImpl!U, U...) && T.dimensions == 2) { 278 size_t w_length = nu_min(lhs.length, rhs.length); 279 280 // NOTE: SSE version of the algorithm. 281 // This algorithm loads 128 bits of mesh data at a time, then deforms it. 282 // Value is stored unaligned to memory. 283 // 284 // TODO: Add aligned version? 285 static if (!SSESizedVectorsAreEmulated) { 286 287 // SIMD version 288 size_t i = 0; 289 for (; i < nu_aligndown(w_length, 2); i += 2) { 290 __m128 xyzw = _mm_load_ps(cast(const(float)*)&lhs[i]); 291 __m128 abcd = _mm_load_ps(cast(const(float)*)&rhs[i]); 292 _mm_storeu_ps(cast(float*)&lhs[i], _mm_mul_ps(xyzw, abcd)); 293 } 294 295 // Tail iteration to finalize the multiplication 296 if (i < w_length) { 297 lhs[i] = lhs[i] * rhs[i]; 298 } 299 } else { 300 301 // Non-SIMD version 302 foreach (i; 0 .. w_length) { 303 lhs[i] = lhs[i] * rhs[i]; 304 } 305 } 306 }