FastLED 3.10.6
Loading...
Searching...
No Matches

◆ sincos32_simd()

FASTLED_FORCE_INLINE SinCos32_simd fl::sincos32_simd ( simd::simd_u32x4 angles)

Process 4 angles simultaneously, returning vectorized sin/cos values SIMD-optimized: vectorized angle decomposition, vector LUT loads with 4x4 transpose (AoS→SoA), and vectorized quadratic interpolation.

Parameters
angles4 u32 angles (0 to 16777216 per angle is a full circle)
Returns
SinCos32_simd with raw i32 values (range: -2147418112 to 2147418112)

Definition at line 32 of file sin32_simd.h.

32 {
33 // ========== Phase 1a: SIMD angle decomposition ==========
34 // Break down each of the 4 angles into components needed for LUT lookup
35
36 // Extract high 8 bits (angle >> 16) → 0..255 range for quadrant + position
37 simd::simd_u32x4 angle256_vec = simd::srl_u32_4(angles, 16);
38
39 // Extract low 16 bits → fractional part for interpolation (0..65535)
40 simd::simd_u32x4 t_vec = simd::and_u32_4(angles, simd::set1_u32_4(0xFFFF));
41
42 // Determine quadrant (0=0°-90°, 1=90°-180°, 2=180°-270°, 3=270°-360°)
43 simd::simd_u32x4 quadrant_vec = simd::srl_u32_4(angle256_vec, 6);
44
45 // Position within quadrant (0..63) → maps to LUT indices 0..63
46 simd::simd_u32x4 pos_vec = simd::and_u32_4(angle256_vec, simd::set1_u32_4(0x3F));
47
48 // Mirror flag: quadrants 1 and 3 need mirrored LUT access
49 simd::simd_u32x4 mirror_s_vec = simd::and_u32_4(quadrant_vec, simd::set1_u32_4(1));
50
51 // ========== Phase 1b: SIMD mask computation ==========
52 // Compute masks for branchless conditional negation in interpolation and sign application
53
54 // Sin derivative mask: 0x00000000 (direct) or 0xFFFFFFFF (negate derivative in mirrored quadrants)
55 simd::simd_u32x4 sdmask_vec = simd::sub_i32_4(simd::set1_u32_4(0), mirror_s_vec);
56
57 // Cos derivative mask: opposite of sin (cos mirrors opposite direction)
58 simd::simd_u32x4 cdmask_vec = simd::xor_u32_4(sdmask_vec, simd::set1_u32_4(0xFFFFFFFF));
59
60 // Sin value sign mask: negative in quadrants 2 and 3 (bit 1 of quadrant)
61 simd::simd_u32x4 quadrant_bit1 = simd::and_u32_4(simd::srl_u32_4(quadrant_vec, 1), simd::set1_u32_4(1));
62 simd::simd_u32x4 svmask_vec = simd::sub_i32_4(simd::set1_u32_4(0), quadrant_bit1);
63
64 // Cos value sign mask: negative in quadrants 1 and 2 (XOR of quadrant bits 0 and 1)
65 simd::simd_u32x4 quadrant_xor = simd::xor_u32_4(quadrant_vec, simd::srl_u32_4(quadrant_vec, 1));
66 simd::simd_u32x4 quadrant_xor_bit0 = simd::and_u32_4(quadrant_xor, simd::set1_u32_4(1));
67 simd::simd_u32x4 cvmask_vec = simd::sub_i32_4(simd::set1_u32_4(0), quadrant_xor_bit0);
68
69 // ========== Phase 1c: Vectorized quarter-wave index mapping ==========
70 // Map position to LUT index using quarter-wave symmetry (branchless mirror logic)
71
72 // Compute qi = pos + mirror * (64 - 2*pos)
73 // This mirrors the position for odd quadrants: pos 0→64, pos 63→1
74 simd::simd_u32x4 two_pos = simd::add_i32_4(pos_vec, pos_vec);
75 simd::simd_u32x4 term_vec = simd::sub_i32_4(simd::set1_u32_4(64), two_pos);
76 simd::simd_u32x4 masked_term = simd::and_u32_4(term_vec, sdmask_vec); // Zero term if not mirrored
77 simd::simd_u32x4 qi_s_vec = simd::add_i32_4(pos_vec, masked_term);
78
79 // Compute qi_next = qi + 1 - 2*mirror
80 // Direct: qi_next = qi + 1 (forward), Mirrored: qi_next = qi - 1 (backward)
81 simd::simd_u32x4 two_mirror_s = simd::add_i32_4(mirror_s_vec, mirror_s_vec);
82 simd::simd_u32x4 qi_next_s_vec = simd::sub_i32_4(simd::add_i32_4(qi_s_vec, simd::set1_u32_4(1)), two_mirror_s);
83
84 // ========== Phase 2: Vector LUT loads + 4x4 transpose (AoS → SoA) ==========
85 // Each LUT entry is 16 bytes: {y_sin, m_sin, y_cos, m_cos} (4 × i32)
86 // We load 4 full entries (one per angle) and transpose to separate vectors
87 // Input: 4 rows × 4 columns (AoS: each row is one angle's data)
88 // Output: 4 columns as separate vectors (SoA: each vector contains same field for 4 angles)
89
90 // Extract indices from SIMD registers for scalar LUT indexing
91 // (SIMD gather is not available on all platforms, so we use scalar indexing)
92 u32 qi0 = simd::extract_u32_4(qi_s_vec, 0);
93 u32 qi1 = simd::extract_u32_4(qi_s_vec, 1);
94 u32 qi2 = simd::extract_u32_4(qi_s_vec, 2);
95 u32 qi3 = simd::extract_u32_4(qi_s_vec, 3);
96
97 u32 qn0 = simd::extract_u32_4(qi_next_s_vec, 0);
98 u32 qn1 = simd::extract_u32_4(qi_next_s_vec, 1);
99 u32 qn2 = simd::extract_u32_4(qi_next_s_vec, 2);
100 u32 qn3 = simd::extract_u32_4(qi_next_s_vec, 3);
101
102 // Load full 16-byte qi entries: {y_sin, m_sin, y_cos, m_cos}
103 // e0 = {y_s0, m_s0, y_c0, m_c0}, e1 = {y_s1, m_s1, y_c1, m_c1}, ...
104 simd::simd_u32x4 e0 = simd::load_u32_4(reinterpret_cast<const u32*>(&sinCosPairedLut[qi0 * 4])); // ok reinterpret cast
105 simd::simd_u32x4 e1 = simd::load_u32_4(reinterpret_cast<const u32*>(&sinCosPairedLut[qi1 * 4])); // ok reinterpret cast
106 simd::simd_u32x4 e2 = simd::load_u32_4(reinterpret_cast<const u32*>(&sinCosPairedLut[qi2 * 4])); // ok reinterpret cast
107 simd::simd_u32x4 e3 = simd::load_u32_4(reinterpret_cast<const u32*>(&sinCosPairedLut[qi3 * 4])); // ok reinterpret cast
108
109 // 4x4 transpose qi entries: AoS {y_s, m_s, y_c, m_c} → SoA
110 // Classic SIMD matrix transpose using interleaved unpacks
111 // Step 1: interleave pairs at 32-bit granularity
112 simd::simd_u32x4 t01lo = simd::unpacklo_u32_4(e0, e1); // {y_s0, y_s1, m_s0, m_s1}
113 simd::simd_u32x4 t01hi = simd::unpackhi_u32_4(e0, e1); // {y_c0, y_c1, m_c0, m_c1}
114 simd::simd_u32x4 t23lo = simd::unpacklo_u32_4(e2, e3); // {y_s2, y_s3, m_s2, m_s3}
115 simd::simd_u32x4 t23hi = simd::unpackhi_u32_4(e2, e3); // {y_c2, y_c3, m_c2, m_c3}
116 // Step 2: final interleave at 64-bit granularity → complete SoA vectors
117 simd::simd_u32x4 y0_s_v = simd::unpacklo_u64_as_u32_4(t01lo, t23lo); // {y_s0, y_s1, y_s2, y_s3}
118 simd::simd_u32x4 m0_s_v = simd::unpackhi_u64_as_u32_4(t01lo, t23lo); // {m_s0, m_s1, m_s2, m_s3}
119 simd::simd_u32x4 y0_c_v = simd::unpacklo_u64_as_u32_4(t01hi, t23hi); // {y_c0, y_c1, y_c2, y_c3}
120 simd::simd_u32x4 m0_c_v = simd::unpackhi_u64_as_u32_4(t01hi, t23hi); // {m_c0, m_c1, m_c2, m_c3}
121
122 // Load full 16-byte qi_next entries (only need y_sin and y_cos for interpolation endpoint)
123 simd::simd_u32x4 n0 = simd::load_u32_4(reinterpret_cast<const u32*>(&sinCosPairedLut[qn0 * 4])); // ok reinterpret cast
124 simd::simd_u32x4 n1 = simd::load_u32_4(reinterpret_cast<const u32*>(&sinCosPairedLut[qn1 * 4])); // ok reinterpret cast
125 simd::simd_u32x4 n2 = simd::load_u32_4(reinterpret_cast<const u32*>(&sinCosPairedLut[qn2 * 4])); // ok reinterpret cast
126 simd::simd_u32x4 n3 = simd::load_u32_4(reinterpret_cast<const u32*>(&sinCosPairedLut[qn3 * 4])); // ok reinterpret cast
127
128 // Partial transpose qi_next: only extract y_sin and y_cos (derivatives not needed)
129 simd::simd_u32x4 n01lo = simd::unpacklo_u32_4(n0, n1); // {y_s0, y_s1, m_s0, m_s1}
130 simd::simd_u32x4 n01hi = simd::unpackhi_u32_4(n0, n1); // {y_c0, y_c1, m_c0, m_c1}
131 simd::simd_u32x4 n23lo = simd::unpacklo_u32_4(n2, n3); // {y_s2, y_s3, m_s2, m_s3}
132 simd::simd_u32x4 n23hi = simd::unpackhi_u32_4(n2, n3); // {y_c2, y_c3, m_c2, m_c3}
133 simd::simd_u32x4 y1_s_v = simd::unpacklo_u64_as_u32_4(n01lo, n23lo); // {y1_s0, y1_s1, y1_s2, y1_s3}
134 simd::simd_u32x4 y1_c_v = simd::unpacklo_u64_as_u32_4(n01hi, n23hi); // {y1_c0, y1_c1, y1_c2, y1_c3}
135
136 // ========== Phase 3: SIMD quadratic interpolation ==========
137 // Vectorized version of scalar interpolation formula from sin32_interp()
138 // P(t) = y0 + T*(m0 + T*(y1 - y0 - m0)) where T = t/65536
139 // Implemented as: c = y1 - y0 - m0; r = c*T + m0; result = r*T + y0
140 //
141 // Optimization: Uses mulhi_su32_4 (signed × unsigned-positive, 11 ops on SSE2)
142 // instead of mulhi_i32_4 (14 ops on SSE2). Safe because t_vec is always in [0, 65535].
143
144 // Sin interpolation
145 // Apply derivative mask: conditionally negate m0 for mirrored quadrants
146 m0_s_v = simd::sub_i32_4(simd::xor_u32_4(m0_s_v, sdmask_vec), sdmask_vec); // (m0 ^ mask) - mask
147 // Compute quadratic coefficient: c = y1 - y0 - m0
148 simd::simd_u32x4 c_s = simd::sub_i32_4(simd::sub_i32_4(y1_s_v, y0_s_v), m0_s_v);
149 // First multiply: r = c*t/65536 + m0 (linear term + derivative)
150 simd::simd_u32x4 r_s = simd::add_i32_4(simd::mulhi_su32_4(c_s, t_vec), m0_s_v);
151 // Second multiply: result = r*t/65536 + y0 (quadratic interpolation complete)
152 simd::simd_u32x4 s_raw = simd::add_i32_4(simd::mulhi_su32_4(r_s, t_vec), y0_s_v);
153
154 // Cos interpolation (identical structure to sin, but uses cos-specific masks and data)
155 m0_c_v = simd::sub_i32_4(simd::xor_u32_4(m0_c_v, cdmask_vec), cdmask_vec); // Conditionally negate m0
156 simd::simd_u32x4 c_c = simd::sub_i32_4(simd::sub_i32_4(y1_c_v, y0_c_v), m0_c_v); // c = y1 - y0 - m0
157 simd::simd_u32x4 r_c = simd::add_i32_4(simd::mulhi_su32_4(c_c, t_vec), m0_c_v); // r = c*t/65536 + m0
158 simd::simd_u32x4 c_raw = simd::add_i32_4(simd::mulhi_su32_4(r_c, t_vec), y0_c_v); // result = r*t/65536 + y0
159
160 // ========== Apply final sign masks ==========
161 // Use branchless negate: (val ^ mask) - mask
162 // If mask = 0x00000000: val unchanged
163 // If mask = 0xFFFFFFFF: val negated (two's complement)
164 SinCos32_simd result;
165 result.sin_vals = simd::sub_i32_4(simd::xor_u32_4(s_raw, svmask_vec), svmask_vec); // Negate if quadrant 2 or 3
166 result.cos_vals = simd::sub_i32_4(simd::xor_u32_4(c_raw, cvmask_vec), cvmask_vec); // Negate if quadrant 1 or 2
167 return result;
168}
platforms::simd_u32x4 simd_u32x4
Definition types.h:26
const i32 sinCosPairedLut[]
expected< T, E > result
Alias for expected (Rust-style naming)
Definition result.h:31

References FASTLED_FORCE_INLINE, FL_NO_EXCEPT, and sinCosPairedLut.

Referenced by fl::Chasing_Spirals_Q31::draw(), and fl::anonymous_namespace{chasing_spirals.cpp.hpp}::simd4_processChannel().

+ Here is the caller graph for this function: