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.

Reply via email to