This is an automated email from the git hooks/post-receive script.
git pushed a commit to branch fix-release-build
in repository efl.
View the commit online.
commit ee766f7fab8c93dd1ac702c2619d646d12bd5327
Author: [email protected] <[email protected]>
AuthorDate: Mon Aug 3 21:11:42 2026 -0600
evas: first AVX2 blend kernel, pixel over dst
mul_256_avx2 and sub4_alpha_avx2 are operation-for-operation ports of
the SSE3 helpers rather than fresh implementations. Every step in the
SSE3 sequence is lane-local on AVX2 - unpack and shuffle_ps both work
within each 128-bit half - so the port computes exactly what SSE3
computes, twice over, and the differential test sees zero delta against
C rather than acceptable rounding.
LOOP_ALIGNED_U1_A8_A16 mirrors the existing A48 macro at 32-byte
alignment with 16- and 8-pixel bodies.
evas_avx2_ops links op_blend_master_avx2.c and op_blend_master_sse3.c
as extra sources (not includes) so their init functions and the
op_blend_span_funcs table they write resolve within the test binary
rather than against libevas.so's own copy - otherwise the AVX2 slots
would silently populate the wrong table and the test would report
zero pairs compared while still exiting successfully. A small stub
supplies ALPHA_255/ALPHA_256, needed by the MMX kernels that get
pulled in unconditionally on this BUILD_MMX host, without dragging in
the unrelated mask/mul compositor externs from evas_blend_main.c.
Review also flagged that this kernel registers
op_blend_span_funcs[SP][SM_N][SC_N][DP_AN][CPU_AVX2], while the SSE3
reference deliberately leaves the equivalent slot commented out with
a bug warning ("BUGGY BUGGY Core i5 750 (32bit) ... ello"). Decision:
keep the AVX2 registration anyway. This slot is one of the 4 bit-exact
C/avx2 pairs in the differential test (maxdelta=0); the DP_AN invariant
holds arithmetically since dest alpha stays 255 for every source alpha
(a + (((256-a)*255)>>8) == 255 at a=0, 128 and 255); and the Core i5
750 named in the SSE3 FIXME is Nehalem - SSE3 but no AVX2 - so it can
never execute this AVX2 path. The historical bug reads as
SSE3-specific 2011-compiler codegen, not a logic error in the blend.
This one kernel, one arithmetic shape (plain premultiplied blend, no
mask/colour), is what the differential test proves at this point.
Later kernel groups need their own verification.
Verified: evas_avx2_ops reports 4 C/avx2 pairs compared, PASS,
maxdelta=0.
Co-Authored-By: Claude Opus 5 (1M context) <[email protected]>
---
.../common/evas_op_blend/op_blend_master_avx2.c | 7 ++
.../common/evas_op_blend/op_blend_pixel_avx2.c | 78 ++++++++++++++++
src/lib/evas/include/evas_blend_ops.h | 101 +++++++++++++++++++++
src/tests/evas/evas_test_simd_avx2_alpha_stub.c | 12 +++
src/tests/evas/meson.build | 22 +++++
5 files changed, 220 insertions(+)
diff --git a/src/lib/evas/common/evas_op_blend/op_blend_master_avx2.c b/src/lib/evas/common/evas_op_blend/op_blend_master_avx2.c
index a9d734179f..49d2450f79 100644
--- a/src/lib/evas/common/evas_op_blend/op_blend_master_avx2.c
+++ b/src/lib/evas/common/evas_op_blend/op_blend_master_avx2.c
@@ -21,10 +21,17 @@ EXPORTAPI void evas_common_cpu_end_opt(void);
extern RGBA_Gfx_Func op_blend_span_funcs[SP_LAST][SM_LAST][SC_LAST][DP_LAST][CPU_LAST];
extern RGBA_Gfx_Func op_blend_rel_span_funcs[SP_LAST][SM_LAST][SC_LAST][DP_LAST][CPU_LAST];
+# include "op_blend_pixel_avx2.c"
+
void
evas_common_op_blend_init_avx2(void)
{
#ifdef BUILD_AVX2
+ GA_MASK_AVX2 = _mm256_set1_epi32(0x00FF00FF);
+ RB_MASK_AVX2 = _mm256_set1_epi32(0xFF00FF00);
+ ALPHA_AVX2 = _mm256_set1_epi32(256);
+
+ init_blend_pixel_span_funcs_avx2();
#endif
}
diff --git a/src/lib/evas/common/evas_op_blend/op_blend_pixel_avx2.c b/src/lib/evas/common/evas_op_blend/op_blend_pixel_avx2.c
new file mode 100644
index 0000000000..664c9b1004
--- /dev/null
+++ b/src/lib/evas/common/evas_op_blend/op_blend_pixel_avx2.c
@@ -0,0 +1,78 @@
+/* blend pixel --> dst */
+
+/* What the differential test does and does not prove for this file: the four
+ * slots registered below are backed by a single kernel function (plus its
+ * SP_AS alias), at one arithmetic shape - plain premultiplied source blended
+ * over dest, no mask, no colour. A bit-exact pass here validates the porting
+ * pattern (mul_256_avx2/sub4_alpha_avx2 as lane-local ports of the SSE3
+ * helpers) and the LOOP_ALIGNED_U1_A8_A16 alignment handling. It does NOT
+ * validate any other arithmetic shape - mask blends, colour blends, the
+ * relative-blend variants, etc. Later kernel groups that copy this file's
+ * pattern each need their own differential-test run against their own C
+ * reference; none of that verification can be inherited from this result.
+ */
+
+#ifdef BUILD_AVX2
+
+static void
+_op_blend_p_dp_avx2(DATA32 *s, DATA8 *m EINA_UNUSED, DATA32 c EINA_UNUSED, DATA32 *d, int l) {
+
+ LOOP_ALIGNED_U1_A8_A16(d, l,
+ { /* UOP */
+
+ int alpha = 256 - (*s >> 24);
+ *d = *s + MUL_256(alpha, *d);
+ s++; d++; l--;
+ },
+ { /* A8OP */
+
+ __m256i s0 = _mm256_loadu_si256((__m256i *)s);
+ __m256i d0 = _mm256_load_si256((__m256i *)d);
+
+ __m256i a0 = sub4_alpha_avx2(s0);
+ __m256i mul0 = mul_256_avx2(a0, d0);
+ d0 = _mm256_add_epi32(mul0, s0);
+
+ _mm256_store_si256((__m256i *)d, d0);
+
+ s += 8; d += 8; l -= 8;
+ },
+ { /* A16OP */
+
+ __m256i s0 = _mm256_loadu_si256((__m256i *)s);
+ __m256i d0 = _mm256_load_si256((__m256i *)d);
+
+ __m256i s1 = _mm256_loadu_si256((__m256i *)(s+8));
+ __m256i d1 = _mm256_load_si256((__m256i *)(d+8));
+
+ __m256i a0 = sub4_alpha_avx2(s0);
+ __m256i a1 = sub4_alpha_avx2(s1);
+
+ __m256i mul0 = mul_256_avx2(a0, d0);
+ __m256i mul1 = mul_256_avx2(a1, d1);
+
+ d0 = _mm256_add_epi32(mul0, s0);
+ d1 = _mm256_add_epi32(mul1, s1);
+
+ _mm256_store_si256((__m256i *)d, d0);
+ _mm256_store_si256((__m256i *)(d+8), d1);
+
+ s += 16; d += 16; l -= 16;
+ })
+}
+
+#define _op_blend_pas_dp_avx2 _op_blend_p_dp_avx2
+
+static void
+init_blend_pixel_span_funcs_avx2(void)
+{
+ op_blend_span_funcs[SP][SM_N][SC_N][DP][CPU_AVX2] = _op_blend_p_dp_avx2;
+ op_blend_span_funcs[SP_AS][SM_N][SC_N][DP][CPU_AVX2] = _op_blend_pas_dp_avx2;
+
+ /* SSE3 leaves this slot disabled (2011 FIXME); AVX2 registers it - bit-exact
+ * vs C, and no pre-Haswell CPU can reach an AVX2 path. */
+ op_blend_span_funcs[SP][SM_N][SC_N][DP_AN][CPU_AVX2] = _op_blend_p_dp_avx2;
+ op_blend_span_funcs[SP_AS][SM_N][SC_N][DP_AN][CPU_AVX2] = _op_blend_pas_dp_avx2;
+}
+
+#endif
diff --git a/src/lib/evas/include/evas_blend_ops.h b/src/lib/evas/include/evas_blend_ops.h
index 82ff0fccd1..5bb8604ecb 100644
--- a/src/lib/evas/include/evas_blend_ops.h
+++ b/src/lib/evas/include/evas_blend_ops.h
@@ -415,6 +415,65 @@ mul3_sym_sse3(__m128i x, __m128i y) {
#endif
#endif
+/* some useful AVX2 inline functions */
+
+#ifdef NEED_AVX2
+#ifdef BUILD_AVX2
+
+#include <immintrin.h>
+
+static __m256i GA_MASK_AVX2;
+static __m256i RB_MASK_AVX2;
+static __m256i ALPHA_AVX2;
+
+#ifndef EFL_ALWAYS_INLINE
+# define EFL_ALWAYS_INLINE inline
+#endif
+
+/* Operation-for-operation port of mul_256_sse3. Every step below is lane-local
+ * - unpacklo/unpackhi and shuffle_ps all work within each 128-bit half on AVX2
+ * - so this computes exactly what the SSE3 helper computes, twice, and the
+ * results are bit-identical rather than merely equivalent. */
+static EFL_ALWAYS_INLINE __m256i
+mul_256_avx2(__m256i a, __m256i c) {
+
+ /* prepare alpha for word multiplication */
+ __m256i a_l = a;
+ __m256i a_h = a;
+ a_l = _mm256_unpacklo_epi16(a_l, a_l);
+ a_h = _mm256_unpackhi_epi16(a_h, a_h);
+ __m256i a0 = (__m256i) _mm256_shuffle_ps( (__m256)a_l, (__m256)a_h, 0x88);
+
+ /* first half of calc */
+ __m256i c0 = c;
+ c0 = _mm256_srli_epi32(c0, 8);
+ c0 = _mm256_and_si256(GA_MASK_AVX2, c0);
+ c0 = _mm256_mullo_epi16(a0, c0);
+ c0 = _mm256_and_si256(RB_MASK_AVX2, c0);
+
+ /* second half of calc */
+ __m256i c1 = c;
+ c1 = _mm256_and_si256(GA_MASK_AVX2, c1);
+ c1 = _mm256_mullo_epi16(a0, c1);
+ c1 = _mm256_srli_epi32(c1, 8);
+ c1 = _mm256_and_si256(GA_MASK_AVX2, c1);
+
+ /* combine */
+ return _mm256_add_epi32(c0, c1);
+}
+
+static EFL_ALWAYS_INLINE __m256i
+sub4_alpha_avx2(__m256i c) {
+
+ __m256i c0 = c;
+
+ c0 = _mm256_srli_epi32(c0, 24);
+ return _mm256_sub_epi32(ALPHA_AVX2, c0);
+}
+
+#endif
+#endif
+
#define LOOP_ALIGNED_U1_A48(DEST, LENGTH, UOP, A4OP, A8OP) \
{ \
while((uintptr_t)DEST & 0xF && LENGTH) UOP \
@@ -441,4 +500,46 @@ mul3_sym_sse3(__m128i x, __m128i y) {
} \
}
+/* Same shape as LOOP_ALIGNED_U1_A48 but for 256-bit kernels: scalar until DEST
+ * reaches a 32-byte boundary, then 16 pixels at a time, then 8, then scalar for
+ * whatever is left. DEST alignment is what lets the destination be loaded and
+ * stored with the aligned intrinsics; sources stay unaligned loads. */
+#define LOOP_ALIGNED_U1_A8_A16(DEST, LENGTH, UOP, A8OP, A16OP) \
+ { \
+ while((uintptr_t)DEST & 0x1F && LENGTH) UOP \
+ \
+ while(LENGTH) { \
+ switch(LENGTH) { \
+ case 7: UOP; EINA_FALLTHROUGH; \
+ case 6: UOP; EINA_FALLTHROUGH; \
+ case 5: UOP; EINA_FALLTHROUGH; \
+ case 4: UOP; EINA_FALLTHROUGH; \
+ case 3: UOP; EINA_FALLTHROUGH; \
+ case 2: UOP; EINA_FALLTHROUGH; \
+ case 1: UOP; \
+ break; \
+ case 15: \
+ EINA_FALLTHROUGH; \
+ case 14: \
+ EINA_FALLTHROUGH; \
+ case 13: \
+ EINA_FALLTHROUGH; \
+ case 12: \
+ EINA_FALLTHROUGH; \
+ case 11: \
+ EINA_FALLTHROUGH; \
+ case 10: \
+ EINA_FALLTHROUGH; \
+ case 9: \
+ EINA_FALLTHROUGH; \
+ case 8: \
+ A8OP \
+ break; \
+ default: \
+ A16OP \
+ break; \
+ } \
+ } \
+ }
+
#endif
diff --git a/src/tests/evas/evas_test_simd_avx2_alpha_stub.c b/src/tests/evas/evas_test_simd_avx2_alpha_stub.c
new file mode 100644
index 0000000000..c6bf5c209e
--- /dev/null
+++ b/src/tests/evas/evas_test_simd_avx2_alpha_stub.c
@@ -0,0 +1,12 @@
+/* evas_test_simd_ops.c pulls in the i386/MMX blend kernels (BUILD_MMX is on
+ * for any x86 config) through its #include of evas_op_blend_main_.c. Those
+ * kernels reference ALPHA_255/ALPHA_256, which are otherwise defined as
+ * non-exported (hidden visibility) globals inside libevas.so's own copy of
+ * evas_blend_main.c. Pulling that whole file in as an extra source drags in
+ * unrelated mask/mul compositor externs, so just provide the two constants
+ * the MMX path actually needs, matching their definition in evas_blend_main.c.
+ */
+#include "evas_common_private.h"
+
+const DATA32 ALPHA_255 = 255;
+const DATA32 ALPHA_256 = 256;
diff --git a/src/tests/evas/meson.build b/src/tests/evas/meson.build
index 55fd1fd86e..41b97736d5 100644
--- a/src/tests/evas/meson.build
+++ b/src/tests/evas/meson.build
@@ -71,3 +71,25 @@ if cpu_neon
timeout : master_timeout
)
endif
+
+# Compares every C/AVX2 pair in the evas blend op tables. Same harness as the
+# NEON build, with the tier taken as a build macro. Needs -mavx2 for the AVX2
+# kernels it pulls in through the op translation unit. op_blend_master_avx2.c
+# is added as an extra source (not #included by evas_test_simd_ops.c) so its
+# evas_common_op_blend_init_avx2() and the op_blend_span_funcs table it writes
+# to resolve within this executable rather than against libevas's copy.
+if cpu_avx2
+ evas_avx2_ops = executable('evas_avx2_ops',
+ ['evas_test_simd_ops.c',
+ '../../lib/evas/common/evas_op_blend/op_blend_master_avx2.c',
+ '../../lib/evas/common/evas_op_blend/op_blend_master_sse3.c',
+ 'evas_test_simd_avx2_alpha_stub.c'],
+ dependencies: [evas_bin, evas, evas_ext_none_static_deps, eet],
+ c_args : ['-DEVAS_BUILD', '-DSIMD_TIER=CPU_AVX2', '-DSIMD_NAME="avx2"'] + avx2_c_args
+ )
+
+ test('evas-avx2-ops', evas_avx2_ops,
+ env : test_env,
+ timeout : master_timeout
+ )
+endif
--
To stop receiving notification emails like this one, please contact
the administrator of this repository.