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 }