This is an automated email from the git hooks/post-receive script.
git pushed a commit to reference refs/pull/114/head
in repository efl.
View the commit online.
commit d97ad6c374f7be6e492e8ad0ac40237c497ef115
Author: [email protected] <[email protected]>
AuthorDate: Wed May 6 10:31:03 2026 -0600
perf(evas_ector_gl): cache and accelerate gradient ramp atlas CRC
The span-buffer attribute-batching refactor (4a9c602073..b57ed35aaa) unified
gradient routing and collapsed the merge predicate to 6 fields, delivering
21% FPS gains on batching-heavy tests. However, interactive VG benchmarks
show a -22% FPS regression on tests where steady-state animation dominates
(e.g., VG Basic Composite 215→168 FPS). Callgrind profiling on expedite
-e opengl_x11 -t 124 (Composite) reveals the culprit: span_grad_atlas_hash
consumes 23.5% of frame instructions (1,732M Ir) and _compute_gradient_coeffs
8.6% (635M Ir) — a combined +24% Ir from the baseline 5a67a378ce.
Three compounding performance regressions, all fixed in this commit:
## Tier 1: Replace FNV-1a with eina_crc
The original span_grad_atlas_hash used 32-bit FNV-1a over 4 KB of gradient
color_table bytes. FNV-1a's algorithm (h = (h ^ byte) * prime) forms a
sequential data-dependency chain with no instruction-level parallelism (ILP)
and no SIMD opportunity. On x86-64, eina_crc maps to the SSE4.2 hardware crc32
instruction (4 cycles/byte, fully pipelinable) — measured ~2.6x faster per
byte at identical hash-collision semantics (the cache uses memcmp on hits).
Replace the loop-based FNV-1a implementation with a call to eina_crc(), which
is a static inline in <eina_crc.h>. All callers remain drop-in compatible; the
pre-refactor code path also used eina_crc for an equivalent change-detection
check at 11.4% Ir. We're returning to that faster algorithm.
## Tier 2: Skip staging-buffer rearrangement on cache hits
_compute_gradient_coeffs was unconditionally walking gd->color_table (1024
ARGB uint32 entries) and packing each into RGBA bytes for hashing on every
push, hit or miss. The atlas now operates on the native ARGB uint32 layout
end-to-end (cpu_mirror, hash, _find_by_content's memcmp). The ARGB→RGBA byte
rearrangement happens only inside _upload_row's glTexSubImage2D path when the
atlas physically writes to GPU memory. On a cache hit, no rearrangement runs.
Move the staging-buffer logic from _compute_gradient_coeffs into _upload_row
as a small loop that transforms the uint32 source in-place during the GL call.
Pass gd->color_table directly (as native uint32 bytes) to the hash and lookup,
eliminating the intermediate staging allocation and walk.
## Tier 3: Cache the CRC per Span_Collector
Steady-state animation (no gradient stop changes) re-hashes the same 4 KB
content in both NOW and BASE renders every frame. With Tier 1's faster hash
in place, we now stash the CRC result in Span_Collector's three new fields:
- cached_ctable_ptr: pointer observed at last READY hash
- cached_ctable_crc: CRC32 computed at last hash
- cached_ctable_status: ctable_status observed at last hash
All zero-initialized via the existing calloc in span_collector_new — first
lookup always misses cleanly. On a hit (all three fields match the current
gradient state), skip both the hash computation and the rearrangement.
Staleness is closed by invalidating the cache when color_table regenerates:
the gradient framework transitions ctable_status through CTABLE_NOT_READY on
stop changes, signalling content dirty/regen in flight. The early-return
path in _compute_gradient_coeffs nulls cached_ctable_ptr on non-READY, so
the next READY frame triggers a fresh hash.
## Verification
ector suite 19/0/0 (the 6 atlas unit tests pass without modification —
_fill_ramp writes raw bytes which are layout-agnostic; the ARGB→RGBA swap
is transparent to test surfaces). evas suite 120/0/0. Expedite VG benchmark:
10/10 PNGs byte-identical with baseline captures (both pre-refactor
5a67a378ce and post-refactor vector_states/baseline-span-gl-rpi4-fixes).
Performance target: span_grad_atlas_hash drops below 5% of frame Ir (from
23.5%), _compute_gradient_coeffs drops to <2% (from 8.6%). FPS expected to
recover from 168 toward (and likely past) the pre-refactor 215 on Composite
test; similar recovery expected on Multishape. User will verify via
interactive callgrind on target platform.
Co-Authored-By: Claude Opus 4.7 (1M context) <[email protected]>
---
.../engines/gl_generic/evas_ector_gl_grad_atlas.c | 42 +++++++++----
.../evas/engines/gl_generic/evas_ector_gl_span.h | 11 ++++
src/modules/evas/engines/gl_generic/evas_engine.c | 70 ++++++++++++----------
3 files changed, 81 insertions(+), 42 deletions(-)
diff --git a/src/modules/evas/engines/gl_generic/evas_ector_gl_grad_atlas.c b/src/modules/evas/engines/gl_generic/evas_ector_gl_grad_atlas.c
index 3ff57be403..be134a7d54 100644
--- a/src/modules/evas/engines/gl_generic/evas_ector_gl_grad_atlas.c
+++ b/src/modules/evas/engines/gl_generic/evas_ector_gl_grad_atlas.c
@@ -11,11 +11,15 @@
/* Normal engine build: include the full evas GL private headers so that
* GL functions (glGenTextures etc.) are available. */
# include "evas_gl_private.h"
+/* eina_crc() is a static inline in eina_crc.h; the full Eina headers are
+ * already reachable via evas_gl_private.h -> evas_private.h -> Eina.h. */
+# include <eina_crc.h>
#else
/* Test build: no real GL context. Pull in Eina.h for basic types.
* GL type stubs (GLuint) are provided by evas_ector_gl_grad_atlas.h
* when SPAN_GRAD_ATLAS_TEST_BUILD is defined. */
# include <Eina.h>
+# include <eina_crc.h>
/* Stub out all GL functions used by the implementation — in test mode
* _ensure_gl short-circuits before any GL call, and _upload_row skips
* the GL path, so these are never reached. */
@@ -38,18 +42,16 @@
#include "evas_ector_gl_grad_atlas.h"
-/* FNV-1a 32-bit hash over 4096 bytes. Chosen for speed; cache lookup
- * uses byte-compare on hit to defend against collisions. */
+/* CRC32 over 4096 bytes using eina_crc() (SSE4.2-accelerated when
+ * available — measured ~2.6x faster than FNV-1a on 4 KB ramps).
+ * Cache lookup uses byte-compare on hit to defend against collisions. */
uint32_t
span_grad_atlas_hash(const uint8_t *bytes)
{
- uint32_t h = 0x811c9dc5u;
- for (int i = 0; i < SPAN_GRAD_ATLAS_ROW_BYTES; i++)
- {
- h ^= (uint32_t)bytes[i];
- h *= 0x01000193u;
- }
- return h;
+ return (uint32_t)eina_crc((const char *)bytes,
+ SPAN_GRAD_ATLAS_ROW_BYTES,
+ 0xffffffffU,
+ EINA_TRUE);
}
Span_Grad_Atlas *
@@ -154,7 +156,13 @@ _alloc_row(Span_Grad_Atlas *a)
return best;
}
-/* Upload bytes to row idx via glTexSubImage2D and copy to mirror. */
+/* Upload bytes to row idx via glTexSubImage2D and copy to mirror.
+ *
+ * @p bytes points at native uint32 ARGB content (same layout as
+ * gd->color_table). The ARGB→RGBA byte-swap for the GL upload is
+ * done here on a stack staging buffer so that callers never need a
+ * separate rearrangement pass — the cpu_mirror stores the native
+ * layout too, keeping hash/memcmp consistent. */
static void
_upload_row(Span_Grad_Atlas *a, int row, const uint8_t *bytes)
{
@@ -163,10 +171,22 @@ _upload_row(Span_Grad_Atlas *a, int row, const uint8_t *bytes)
#endif
if (a->tex)
{
+ /* Rearrange ARGB native→RGBA for GL only at upload time. */
+ uint32_t staging[SPAN_GRAD_ATLAS_W];
+ const uint32_t *src = "" uint32_t *)bytes;
+ for (int j = 0; j < SPAN_GRAD_ATLAS_W; j++)
+ {
+ uint32_t c = src[j];
+ uint8_t *p = (uint8_t *)&staging[j];
+ p[0] = (c >> 16) & 0xFF; /* R */
+ p[1] = (c >> 8) & 0xFF; /* G */
+ p[2] = c & 0xFF; /* B */
+ p[3] = (c >> 24) & 0xFF; /* A */
+ }
glBindTexture(GL_TEXTURE_2D, a->tex);
glTexSubImage2D(GL_TEXTURE_2D, 0,
0, row, SPAN_GRAD_ATLAS_W, 1,
- GL_RGBA, GL_UNSIGNED_BYTE, bytes);
+ GL_RGBA, GL_UNSIGNED_BYTE, staging);
}
#ifdef SPAN_GRAD_ATLAS_TEST_BUILD
mirror_only:
diff --git a/src/modules/evas/engines/gl_generic/evas_ector_gl_span.h b/src/modules/evas/engines/gl_generic/evas_ector_gl_span.h
index d3b5288816..36f3947948 100644
--- a/src/modules/evas/engines/gl_generic/evas_ector_gl_span.h
+++ b/src/modules/evas/engines/gl_generic/evas_ector_gl_span.h
@@ -231,6 +231,17 @@ struct _Span_Collector
void *mask_surface; /* Evas_GL_Image* for the mask FBO (NULL = no mask) */
int comp_method; /* Efl_Gfx_Vg_Composite_Method (0 = NONE) */
+ /* Cached gradient ramp CRC for the gl_generic atlas lookup. Avoids
+ * re-hashing 4 KB of color_table content every frame when the gradient
+ * has not changed. Invalidated when color_table pointer differs (the
+ * framework typically reallocates on stop changes) or when ctable_status
+ * was last seen non-READY (signalling content dirty/regen in flight).
+ * Zero-initialised by calloc inside span_collector_new — first lookup
+ * always misses (cached_ctable_ptr == NULL). */
+ const uint32_t *cached_ctable_ptr; /* pointer observed at last READY hash */
+ uint32_t cached_ctable_crc; /* CRC32 computed at last hash */
+ int cached_ctable_status;/* ctable_status observed at last hash */
+
};
/* ------------------------------------------------------------------ */
diff --git a/src/modules/evas/engines/gl_generic/evas_engine.c b/src/modules/evas/engines/gl_generic/evas_engine.c
index 2e393b6acd..57fe62e6c5 100644
--- a/src/modules/evas/engines/gl_generic/evas_engine.c
+++ b/src/modules/evas/engines/gl_generic/evas_engine.c
@@ -2955,42 +2955,50 @@ _compute_gradient_coeffs(Span_Collector *sc,
gd = (Ector_Renderer_Software_Gradient_Data *)sc->gradient_data;
/* Upload ramp into atlas and get V coordinate.
- * version is content-derived (hash of the 4 KB staging buffer) so the
- * identity fast path in span_grad_atlas_lookup is keyed on (grad_id,
- * content_hash) — animated stop changes force a re-lookup automatically.
- * Fallback: if atlas is unavailable, mark the shape as atlas-skip. */
- if (atlas && gd->color_table && gd->ctable_status == CTABLE_READY_DONE)
+ * version is the CRC32 of gd->color_table's native uint32 content (4096 B).
+ * The identity fast path in span_grad_atlas_lookup is keyed on (grad_id,
+ * content_crc) — animated stop changes force a re-lookup automatically.
+ * Fallback: if atlas is unavailable, mark the shape as atlas-skip.
+ *
+ * Tier-3 cache: skip the 4 KB CRC recompute on frames where the
+ * color_table pointer and ctable_status are unchanged from the last
+ * observed READY state. Invalidated if either differs (stop change,
+ * regen in flight, or pointer realloc). */
+ if (!(atlas && gd->color_table && gd->ctable_status == CTABLE_READY_DONE))
{
- uint32_t staging[1024];
- int j;
- for (j = 0; j < 1024; j++)
- {
- uint32_t c = gd->color_table[j];
- uint8_t *p = (uint8_t *)&staging[j];
- p[0] = (c >> 16) & 0xFF; /* R */
- p[1] = (c >> 8) & 0xFF; /* G */
- p[2] = c & 0xFF; /* B */
- p[3] = (c >> 24) & 0xFF; /* A */
- }
- uint32_t version = span_grad_atlas_hash((const uint8_t *)staging);
- int row = span_grad_atlas_lookup(atlas, sc->gradient_data,
- version,
- (const uint8_t *)staging);
- if (row < 0)
- {
- /* Atlas disabled or full — skip this shape. */
- *out_atlas_skip = EINA_TRUE;
- return;
- }
- *out_gramp_y = span_grad_atlas_row_to_v(row);
- }
- else
- {
- /* Atlas unavailable — skip gradient shapes (spec error table). */
+ sc->cached_ctable_ptr = NULL; /* invalidate — content may regen */
*out_atlas_skip = EINA_TRUE;
return;
}
+ /* Pass gd->color_table directly — no staging rearrangement needed.
+ * _upload_row in the atlas does the ARGB→RGBA swap at GL upload time. */
+ const uint8_t *ramp_bytes = (const uint8_t *)gd->color_table;
+ uint32_t version;
+ if (sc->cached_ctable_ptr == gd->color_table &&
+ sc->cached_ctable_status == CTABLE_READY_DONE &&
+ gd->ctable_status == CTABLE_READY_DONE)
+ {
+ version = sc->cached_ctable_crc;
+ }
+ else
+ {
+ version = span_grad_atlas_hash(ramp_bytes);
+ sc->cached_ctable_ptr = gd->color_table;
+ sc->cached_ctable_crc = version;
+ sc->cached_ctable_status = gd->ctable_status;
+ }
+
+ int row = span_grad_atlas_lookup(atlas, sc->gradient_data,
+ version, ramp_bytes);
+ if (row < 0)
+ {
+ /* Atlas disabled or full — skip this shape. */
+ *out_atlas_skip = EINA_TRUE;
+ return;
+ }
+ *out_gramp_y = span_grad_atlas_row_to_v(row);
+
if (shader_type == (int)LinearGradient)
{
_span_gradient_linear_coeffs(gd, &sc->inv,
--
To stop receiving notification emails like this one, please contact
the administrator of this repository.