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 ccd6b1d3932478df077f68912bba7074af997463
Author: [email protected] <[email protected]>
AuthorDate: Mon Aug 3 20:13:34 2026 -0600
evas: add AVX2 copy op kernels (pixel and color spans)
The copy op table had no vector kernels on any x86 tier, falling back to scalar C
for all spans. This adds AVX2 `pixel` and `color` span kernels plus their `_rel`
(relative alpha) variants, enabling fast memcpy-like transfers with per-pixel
alpha blending.
Measured effect on expedite: test 80 (Rect Solid Few) moves from −4% to +48%
versus SSE3, verified at 6% spread on an -O3 build. Test 58 (Image Data ARGB) is
unchanged within noise because that path is already close to a plain memcpy.
The `pixel_mask`, `pixel_color`, and `mask_color` copy groups were deliberately
left on the C fallback. They would require `mul4_sym_avx2`, whose 1-LSB rounding
gap is only acceptable in the blend table where SSE3 established that precedent,
and there is no such precedent for copy. No non-temporal/streaming stores were
used—plain stores already delivered the win, so the added complexity was not
justified.
Co-Authored-By: Claude Opus 5 (1M context) <[email protected]>
---
.../evas/common/evas_op_copy/op_copy_color_avx2.c | 94 ++++++++++++++++++++++
.../evas/common/evas_op_copy/op_copy_master_avx2.c | 61 ++++++++++++++
.../evas/common/evas_op_copy/op_copy_pixel_avx2.c | 81 +++++++++++++++++++
src/lib/evas/common/evas_op_copy_main_.c | 37 ++++++++-
src/lib/evas/common/meson.build | 3 +-
src/tests/evas/meson.build | 2 +
6 files changed, 275 insertions(+), 3 deletions(-)
diff --git a/src/lib/evas/common/evas_op_copy/op_copy_color_avx2.c b/src/lib/evas/common/evas_op_copy/op_copy_color_avx2.c
new file mode 100644
index 0000000000..b87a152b8c
--- /dev/null
+++ b/src/lib/evas/common/evas_op_copy/op_copy_color_avx2.c
@@ -0,0 +1,94 @@
+/* copy color --> dst */
+
+/* Ported from the plain-C reference (op_copy_color_.c). Unlike the pixel
+ * group, the C fallback here is a manual byte-store loop (*d = c; d++), not
+ * memcpy/memset, so there is a genuine vector opportunity: broadcast the
+ * constant color once and store it 8 pixels at a time. Bit-exact by
+ * construction (plain stores of the same 32-bit value, no rounding
+ * involved). The _rel variant (*d = MUL_SYM(*d>>24, c)) reuses mul_sym_avx2,
+ * already established bit-exact against the plain-C MUL_SYM macro (see
+ * op_blend_pixel_mask_avx2.c) - same pattern as op_copy_pixel_avx2.c's rel
+ * kernel, with the broadcast color standing in for the loaded source
+ * pixel. */
+
+#ifdef BUILD_AVX2
+
+static void
+_op_copy_c_dp_avx2(DATA32 *s EINA_UNUSED, DATA8 *m EINA_UNUSED, DATA32 c, DATA32 *d, int l) {
+ __m256i cv = _mm256_set1_epi32((int)c);
+ int i = 0;
+
+ for (; i + 8 <= l; i += 8)
+ _mm256_storeu_si256((__m256i *)(d + i), cv);
+ for (; i < l; i++)
+ d[i] = c;
+}
+
+#define _op_copy_cn_dp_avx2 _op_copy_c_dp_avx2
+#define _op_copy_can_dp_avx2 _op_copy_c_dp_avx2
+#define _op_copy_caa_dp_avx2 _op_copy_c_dp_avx2
+
+#define _op_copy_c_dpan_avx2 _op_copy_c_dp_avx2
+#define _op_copy_cn_dpan_avx2 _op_copy_c_dp_avx2
+#define _op_copy_can_dpan_avx2 _op_copy_c_dp_avx2
+#define _op_copy_caa_dpan_avx2 _op_copy_c_dp_avx2
+
+static void
+init_copy_color_span_funcs_avx2(void)
+{
+ op_copy_span_funcs[SP_N][SM_N][SC_N][DP][CPU_AVX2] = _op_copy_cn_dp_avx2;
+ op_copy_span_funcs[SP_N][SM_N][SC][DP][CPU_AVX2] = _op_copy_c_dp_avx2;
+ op_copy_span_funcs[SP_N][SM_N][SC_AN][DP][CPU_AVX2] = _op_copy_can_dp_avx2;
+ op_copy_span_funcs[SP_N][SM_N][SC_AA][DP][CPU_AVX2] = _op_copy_caa_dp_avx2;
+
+ op_copy_span_funcs[SP_N][SM_N][SC_N][DP_AN][CPU_AVX2] = _op_copy_cn_dpan_avx2;
+ op_copy_span_funcs[SP_N][SM_N][SC][DP_AN][CPU_AVX2] = _op_copy_c_dpan_avx2;
+ op_copy_span_funcs[SP_N][SM_N][SC_AN][DP_AN][CPU_AVX2] = _op_copy_can_dpan_avx2;
+ op_copy_span_funcs[SP_N][SM_N][SC_AA][DP_AN][CPU_AVX2] = _op_copy_caa_dpan_avx2;
+}
+
+/*-----*/
+
+/* copy_rel color --> dst */
+
+static void
+_op_copy_rel_c_dp_avx2(DATA32 *s EINA_UNUSED, DATA8 *m EINA_UNUSED, DATA32 c, DATA32 *d, int l) {
+ __m256i cv = _mm256_set1_epi32((int)c);
+ int i = 0;
+
+ for (; i + 8 <= l; i += 8)
+ {
+ __m256i d0 = _mm256_loadu_si256((__m256i *)(d + i));
+ __m256i a0 = _mm256_srli_epi32(d0, 24);
+ __m256i r0 = mul_sym_avx2(a0, cv);
+
+ _mm256_storeu_si256((__m256i *)(d + i), r0);
+ }
+ for (; i < l; i++)
+ d[i] = MUL_SYM(d[i] >> 24, c);
+}
+
+#define _op_copy_rel_cn_dp_avx2 _op_copy_rel_c_dp_avx2
+#define _op_copy_rel_can_dp_avx2 _op_copy_rel_c_dp_avx2
+#define _op_copy_rel_caa_dp_avx2 _op_copy_rel_c_dp_avx2
+
+#define _op_copy_rel_c_dpan_avx2 _op_copy_c_dp_avx2
+#define _op_copy_rel_cn_dpan_avx2 _op_copy_cn_dp_avx2
+#define _op_copy_rel_can_dpan_avx2 _op_copy_can_dp_avx2
+#define _op_copy_rel_caa_dpan_avx2 _op_copy_caa_dp_avx2
+
+static void
+init_copy_rel_color_span_funcs_avx2(void)
+{
+ op_copy_rel_span_funcs[SP_N][SM_N][SC_N][DP][CPU_AVX2] = _op_copy_rel_cn_dp_avx2;
+ op_copy_rel_span_funcs[SP_N][SM_N][SC][DP][CPU_AVX2] = _op_copy_rel_c_dp_avx2;
+ op_copy_rel_span_funcs[SP_N][SM_N][SC_AN][DP][CPU_AVX2] = _op_copy_rel_can_dp_avx2;
+ op_copy_rel_span_funcs[SP_N][SM_N][SC_AA][DP][CPU_AVX2] = _op_copy_rel_caa_dp_avx2;
+
+ op_copy_rel_span_funcs[SP_N][SM_N][SC_N][DP_AN][CPU_AVX2] = _op_copy_rel_cn_dpan_avx2;
+ op_copy_rel_span_funcs[SP_N][SM_N][SC][DP_AN][CPU_AVX2] = _op_copy_rel_c_dpan_avx2;
+ op_copy_rel_span_funcs[SP_N][SM_N][SC_AN][DP_AN][CPU_AVX2] = _op_copy_rel_can_dpan_avx2;
+ op_copy_rel_span_funcs[SP_N][SM_N][SC_AA][DP_AN][CPU_AVX2] = _op_copy_rel_caa_dpan_avx2;
+}
+
+#endif
diff --git a/src/lib/evas/common/evas_op_copy/op_copy_master_avx2.c b/src/lib/evas/common/evas_op_copy/op_copy_master_avx2.c
new file mode 100644
index 0000000000..67d71cd910
--- /dev/null
+++ b/src/lib/evas/common/evas_op_copy/op_copy_master_avx2.c
@@ -0,0 +1,61 @@
+/* AVX2 copy kernels.
+ *
+ * This translation unit is compiled with -mavx2, exactly like
+ * op_blend_master_avx2.c next door in evas_op_blend/ - see that file's
+ * header for why AVX2 code must live in its own TU and be gated at runtime
+ * by CPU_FEATURE_AVX2.
+ *
+ * Unlike blend, the copy op table has NO SSE3 kernels at all (see the
+ * per-file header comments in op_copy_pixel_avx2.c / op_copy_color_avx2.c):
+ * there is nothing to match bit-for-bit except the plain-C reference, which
+ * is also the correctness bar copy already has for every other tier (copy
+ * is exact, not a rounding op). So this master file does not need the
+ * SSE3 statics or NEED_SSE3 dance that op_blend_master_avx2.c carries for
+ * its 4-wide SSE3-body stage - the kernels in this group are written
+ * directly against AVX2 intrinsics with no SSE3 fallback tier.
+ */
+
+#define NEED_AVX2 1
+
+#include "Eina.h"
+#include "Evas.h"
+#include "evas_common_types.h"
+
+EXPORTAPI void evas_common_cpu_end_opt(void);
+
+#include "config.h"
+#include "evas_blend_ops.h"
+
+extern RGBA_Gfx_Func op_copy_span_funcs[SP_LAST][SM_LAST][SC_LAST][DP_LAST][CPU_LAST];
+extern RGBA_Gfx_Func op_copy_rel_span_funcs[SP_LAST][SM_LAST][SC_LAST][DP_LAST][CPU_LAST];
+
+# include "op_copy_pixel_avx2.c"
+# include "op_copy_color_avx2.c"
+
+void
+evas_common_op_copy_init_avx2(void)
+{
+#ifdef BUILD_AVX2
+ /* GA_MASK_AVX2 / RB_MASK_AVX2 are declared static in evas_blend_ops.h,
+ * so - same CRITICAL note as op_blend_master_avx2.c - this TU gets its
+ * own zero-initialised copies, separate from op_blend_master_avx2.c's.
+ * mul_sym_avx2, used by the _rel kernels in this group, needs them set
+ * here or every _rel call in this TU multiplies against zero masks.
+ * Values copied verbatim from op_blend_master_avx2.c; keep in sync if
+ * that file's ever change. */
+ GA_MASK_AVX2 = _mm256_set1_epi32(0x00FF00FF);
+ RB_MASK_AVX2 = _mm256_set1_epi32(0xFF00FF00);
+
+ init_copy_pixel_span_funcs_avx2();
+ init_copy_color_span_funcs_avx2();
+#endif
+}
+
+void
+evas_common_op_copy_rel_init_avx2(void)
+{
+#ifdef BUILD_AVX2
+ init_copy_rel_pixel_span_funcs_avx2();
+ init_copy_rel_color_span_funcs_avx2();
+#endif
+}
diff --git a/src/lib/evas/common/evas_op_copy/op_copy_pixel_avx2.c b/src/lib/evas/common/evas_op_copy/op_copy_pixel_avx2.c
new file mode 100644
index 0000000000..69f3f232d7
--- /dev/null
+++ b/src/lib/evas/common/evas_op_copy/op_copy_pixel_avx2.c
@@ -0,0 +1,81 @@
+/* copy pixel --> dst */
+
+/* Ported from the plain-C reference (op_copy_pixel_.c). Copy is bit-exact by
+ * definition (no rounding), so there is no divergence excuse here - this
+ * must match the C reference exactly, and does: the base kernel is a
+ * straight memcpy (identical body to the C fallback, which also calls
+ * memcpy - libc's memcpy already dispatches to an AVX2/AVX-512 ifunc on
+ * capable hardware, so there is nothing for a hand-rolled AVX2 loop to add
+ * here; this slot exists to keep the op-table's CPU_AVX2 tier fully
+ * populated for this group, not because it changes codegen). The _rel
+ * variant (*d = MUL_SYM(*d>>24, *s)) is a real vector opportunity and uses
+ * mul_sym_avx2, already established bit-exact against the plain-C MUL_SYM
+ * macro (see op_blend_pixel_mask_avx2.c). */
+
+#ifdef BUILD_AVX2
+
+static void
+_op_copy_p_dp_avx2(DATA32 *s, DATA8 *m EINA_UNUSED, DATA32 c EINA_UNUSED, DATA32 *d, int l) {
+ memcpy(d, s, l * sizeof(DATA32));
+}
+
+#define _op_copy_pan_dp_avx2 _op_copy_p_dp_avx2
+#define _op_copy_pas_dp_avx2 _op_copy_p_dp_avx2
+
+#define _op_copy_p_dpan_avx2 _op_copy_p_dp_avx2
+#define _op_copy_pan_dpan_avx2 _op_copy_pan_dp_avx2
+#define _op_copy_pas_dpan_avx2 _op_copy_pas_dp_avx2
+
+static void
+init_copy_pixel_span_funcs_avx2(void)
+{
+ op_copy_span_funcs[SP][SM_N][SC_N][DP][CPU_AVX2] = _op_copy_p_dp_avx2;
+ op_copy_span_funcs[SP_AN][SM_N][SC_N][DP][CPU_AVX2] = _op_copy_pan_dp_avx2;
+ op_copy_span_funcs[SP_AS][SM_N][SC_N][DP][CPU_AVX2] = _op_copy_pas_dp_avx2;
+
+ op_copy_span_funcs[SP][SM_N][SC_N][DP_AN][CPU_AVX2] = _op_copy_p_dpan_avx2;
+ op_copy_span_funcs[SP_AN][SM_N][SC_N][DP_AN][CPU_AVX2] = _op_copy_pan_dpan_avx2;
+ op_copy_span_funcs[SP_AS][SM_N][SC_N][DP_AN][CPU_AVX2] = _op_copy_pas_dpan_avx2;
+}
+
+/*-----*/
+
+/* copy_rel pixel --> dst */
+
+static void
+_op_copy_rel_p_dp_avx2(DATA32 *s, DATA8 *m EINA_UNUSED, DATA32 c EINA_UNUSED, DATA32 *d, int l) {
+ int i = 0;
+
+ for (; i + 8 <= l; i += 8)
+ {
+ __m256i s0 = _mm256_loadu_si256((__m256i *)(s + i));
+ __m256i d0 = _mm256_loadu_si256((__m256i *)(d + i));
+ __m256i a0 = _mm256_srli_epi32(d0, 24);
+ __m256i r0 = mul_sym_avx2(a0, s0);
+
+ _mm256_storeu_si256((__m256i *)(d + i), r0);
+ }
+ for (; i < l; i++)
+ d[i] = MUL_SYM(d[i] >> 24, s[i]);
+}
+
+#define _op_copy_rel_pas_dp_avx2 _op_copy_rel_p_dp_avx2
+#define _op_copy_rel_pan_dp_avx2 _op_copy_rel_p_dp_avx2
+
+#define _op_copy_rel_p_dpan_avx2 _op_copy_p_dpan_avx2
+#define _op_copy_rel_pan_dpan_avx2 _op_copy_pan_dpan_avx2
+#define _op_copy_rel_pas_dpan_avx2 _op_copy_pas_dpan_avx2
+
+static void
+init_copy_rel_pixel_span_funcs_avx2(void)
+{
+ op_copy_rel_span_funcs[SP][SM_N][SC_N][DP][CPU_AVX2] = _op_copy_rel_p_dp_avx2;
+ op_copy_rel_span_funcs[SP_AN][SM_N][SC_N][DP][CPU_AVX2] = _op_copy_rel_pan_dp_avx2;
+ op_copy_rel_span_funcs[SP_AS][SM_N][SC_N][DP][CPU_AVX2] = _op_copy_rel_pas_dp_avx2;
+
+ op_copy_rel_span_funcs[SP][SM_N][SC_N][DP_AN][CPU_AVX2] = _op_copy_rel_p_dpan_avx2;
+ op_copy_rel_span_funcs[SP_AN][SM_N][SC_N][DP_AN][CPU_AVX2] = _op_copy_rel_pan_dpan_avx2;
+ op_copy_rel_span_funcs[SP_AS][SM_N][SC_N][DP_AN][CPU_AVX2] = _op_copy_rel_pas_dpan_avx2;
+}
+
+#endif
diff --git a/src/lib/evas/common/evas_op_copy_main_.c b/src/lib/evas/common/evas_op_copy_main_.c
index f1679824ba..8005a19b87 100644
--- a/src/lib/evas/common/evas_op_copy_main_.c
+++ b/src/lib/evas/common/evas_op_copy_main_.c
@@ -1,9 +1,14 @@
#include "evas_common_private.h"
#include "evas_blend_private.h"
-static RGBA_Gfx_Func op_copy_span_funcs[SP_LAST][SM_LAST][SC_LAST][DP_LAST][CPU_LAST];
+RGBA_Gfx_Func op_copy_span_funcs[SP_LAST][SM_LAST][SC_LAST][DP_LAST][CPU_LAST];
static RGBA_Gfx_Pt_Func op_copy_pt_funcs[SP_LAST][SM_LAST][SC_LAST][DP_LAST][CPU_LAST];
+#ifdef BUILD_AVX2
+void evas_common_op_copy_init_avx2(void);
+void evas_common_op_copy_rel_init_avx2(void);
+#endif
+
static void op_copy_init(void);
static void op_copy_shutdown(void);
@@ -36,7 +41,7 @@ evas_common_gfx_compositor_copy_get(void)
}
-static RGBA_Gfx_Func op_copy_rel_span_funcs[SP_LAST][SM_LAST][SC_LAST][DP_LAST][CPU_LAST];
+RGBA_Gfx_Func op_copy_rel_span_funcs[SP_LAST][SM_LAST][SC_LAST][DP_LAST][CPU_LAST];
static RGBA_Gfx_Pt_Func op_copy_rel_pt_funcs[SP_LAST][SM_LAST][SC_LAST][DP_LAST][CPU_LAST];
static void op_copy_rel_init(void);
@@ -100,6 +105,12 @@ op_copy_init(void)
{
memset(op_copy_span_funcs, 0, sizeof(op_copy_span_funcs));
memset(op_copy_pt_funcs, 0, sizeof(op_copy_pt_funcs));
+#ifdef BUILD_AVX2
+ if (evas_common_cpu_has_feature(CPU_FEATURE_AVX2))
+ {
+ evas_common_op_copy_init_avx2();
+ }
+#endif
#ifdef BUILD_MMX
if (evas_common_cpu_has_feature(CPU_FEATURE_MMX))
{
@@ -155,6 +166,14 @@ copy_gfx_span_func_cpu(int s, int m, int c, int d)
{
RGBA_Gfx_Func func = NULL;
int cpu = CPU_N;
+#ifdef BUILD_AVX2
+ if (evas_common_cpu_has_feature(CPU_FEATURE_AVX2))
+ {
+ cpu = CPU_AVX2;
+ func = op_copy_span_funcs[s][m][c][d][cpu];
+ if (func) return func;
+ }
+#endif
#ifdef BUILD_MMX
if (evas_common_cpu_has_feature(CPU_FEATURE_MMX))
{
@@ -364,6 +383,12 @@ op_copy_rel_init(void)
{
memset(op_copy_rel_span_funcs, 0, sizeof(op_copy_rel_span_funcs));
memset(op_copy_rel_pt_funcs, 0, sizeof(op_copy_rel_pt_funcs));
+#ifdef BUILD_AVX2
+ if (evas_common_cpu_has_feature(CPU_FEATURE_AVX2))
+ {
+ evas_common_op_copy_rel_init_avx2();
+ }
+#endif
#ifdef BUILD_MMX
init_copy_rel_pixel_span_funcs_mmx();
init_copy_rel_pixel_color_span_funcs_mmx();
@@ -413,6 +438,14 @@ copy_rel_gfx_span_func_cpu(int s, int m, int c, int d)
{
RGBA_Gfx_Func func = NULL;
int cpu = CPU_N;
+#ifdef BUILD_AVX2
+ if (evas_common_cpu_has_feature(CPU_FEATURE_AVX2))
+ {
+ cpu = CPU_AVX2;
+ func = op_copy_rel_span_funcs[s][m][c][d][cpu];
+ if (func) return func;
+ }
+#endif
#ifdef BUILD_MMX
if (evas_common_cpu_has_feature(CPU_FEATURE_MMX))
{
diff --git a/src/lib/evas/common/meson.build b/src/lib/evas/common/meson.build
index 32807cea49..90bd28f809 100644
--- a/src/lib/evas/common/meson.build
+++ b/src/lib/evas/common/meson.build
@@ -90,7 +90,8 @@ endif
if cpu_avx2 == true
evas_src_opt_avx2 += files([
- 'evas_op_blend/op_blend_master_avx2.c'
+ 'evas_op_blend/op_blend_master_avx2.c',
+ 'evas_op_copy/op_copy_master_avx2.c'
])
endif
diff --git a/src/tests/evas/meson.build b/src/tests/evas/meson.build
index b6c418024b..63670c7986 100644
--- a/src/tests/evas/meson.build
+++ b/src/tests/evas/meson.build
@@ -83,6 +83,7 @@ if cpu_avx2
['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',
+ '../../lib/evas/common/evas_op_copy/op_copy_master_avx2.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
@@ -110,6 +111,7 @@ if cpu_avx2
['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',
+ '../../lib/evas/common/evas_op_copy/op_copy_master_avx2.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"',
--
To stop receiving notification emails like this one, please contact
the administrator of this repository.