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 }