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 a7a36afcf45d4e94f2dff7a213aa95345409f7ad
Author: [email protected] <[email protected]>
AuthorDate: Wed May 6 11:03:09 2026 -0600

    perf(evas_ector_gl): cache ramp CRC on gradient data, drop atlas re-hash
    
    Callgrind profiling of the regressed VG tests (120, 123-126) on expedite
    revealed that _eina_crc was consuming 16-22% of frame Ir, with a telltale
    pattern: fixed cost per push regardless of content, and exactly 2× the
    pre-refactor baseline.  This pinpointed two distinct inefficiencies in
    the Tier-3 cache added in d97ad6c374.
    
    ## Fix 1: eliminate redundant hash in span_grad_atlas_lookup
    
    span_grad_atlas_lookup already receives the CRC via the 'version' parameter,
    computed by the caller as span_grad_atlas_hash(bytes).  On identity miss,
    the function was re-hashing those same bytes to drive _find_by_content.
    By contract, version == h, so the second hash was redundant.
    
    Replace uint32_t h = span_grad_atlas_hash(bytes) with uint32_t h = version.
    Saves one 4 KB CRC per identity miss, which occurs every frame for any
    gradient not explicitly hashed in the current frame's collect pass.
    
    ## Fix 2: relocate gradient-ramp CRC cache from Span_Collector to gd
    
    The cache lived on Span_Collector, which is a per-frame slot pool managed
    by a high-water mark allocator: slot[0] may serve shape A in frame N,
    shape B in frame N+1.  The cache fields, keyed implicitly on the slot,
    thus invalidated every frame and never hit.
    
    Move cached_ctable_crc, cached_ctable_status, and cached_ctable_crc_valid
    onto Ector_Renderer_Software_Gradient_Data (the gradient's Eo private data,
    stable for the object's lifetime).  This gives the cache a stable home.
    
    Staleness is invalidated via the existing early-return path: when
    _compute_gradient_coeffs early-returns because ctable_status isn't READY,
    it now sets gd->cached_ctable_crc_valid = EINA_FALSE.  The framework
    cycles status through CTABLE_NOT_READY whenever stops change, so any
    content change forces the next READY frame to recompute the CRC.
    
    Cross-module placement note: putting GL-engine cache fields on a struct
    in lib/ector/software/ crosses a layer boundary, but is acceptable because
    the fields are opaque cache state with no API surface and the struct lives
    in a private header only by the gradient renderer subtree.
    
    Verification:
    - ector suite 19/0/0
    - evas suite 120/0/0
    - Expedite VG: 10/10 PNGs byte-identical with baseline
    - Build clean
    - Expected: _eina_crc Ir on regressed tests drops to cache-miss cost only
      (first frame per gradient + upload memcmp), recovering remaining FPS
      regression on those tests
    
    Co-Authored-By: Claude Opus 4.7 (1M context) <[email protected]>
---
 src/lib/ector/software/ector_software_private.h    | 16 +++++++++++++
 .../engines/gl_generic/evas_ector_gl_grad_atlas.c  |  7 ++++--
 .../evas/engines/gl_generic/evas_ector_gl_span.h   | 11 ---------
 src/modules/evas/engines/gl_generic/evas_engine.c  | 27 ++++++++++++++--------
 4 files changed, 39 insertions(+), 22 deletions(-)

diff --git a/src/lib/ector/software/ector_software_private.h b/src/lib/ector/software/ector_software_private.h
index fe74bca515..283394549e 100644
--- a/src/lib/ector/software/ector_software_private.h
+++ b/src/lib/ector/software/ector_software_private.h
@@ -49,6 +49,22 @@ typedef struct _Ector_Renderer_Software_Gradient_Data
 
    Eina_Bool alpha;
    int ctable_status;       //Ready for color table?
+
+   /* GL atlas CRC cache: avoids re-hashing 4 KB of color_table on every
+    * span push when the ramp content has not changed since the previous
+    * compute.  Invalidated when the framework regenerates color_table
+    * (status cycles through CTABLE_NOT_READY before becoming READY again).
+    *
+    * Lives here rather than on Span_Collector so the cache survives the
+    * collector pool's high-water-mark slot reuse — collectors are
+    * recycled across shapes between frames, but gradient_data is stable
+    * per-gradient-renderer-object.
+    *
+    * Zero-initialised automatically: Eo private data is calloc'd by the
+    * Eo framework, so cached_ctable_crc_valid starts as EINA_FALSE. */
+   uint32_t  cached_ctable_crc;
+   int       cached_ctable_status;   /* ctable_status when crc was computed */
+   Eina_Bool cached_ctable_crc_valid;
 } Ector_Renderer_Software_Gradient_Data;
 
 typedef struct _Shape_Rle_Data
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 be134a7d54..9db85de342 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
@@ -209,8 +209,11 @@ span_grad_atlas_lookup(Span_Grad_Atlas *a, void *grad_id,
         return row;
      }
 
-   /* Hash + byte-compare path. */
-   uint32_t h = span_grad_atlas_hash(bytes);
+   /* Hash + byte-compare path.
+    * version == span_grad_atlas_hash(bytes) by contract: callers compute it
+    * via span_grad_atlas_hash() before calling lookup, so recomputing here
+    * is redundant.  Use version directly. */
+   uint32_t h = version;
    row = _find_by_content(a, h, bytes);
    if (row >= 0)
      {
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 36f3947948..d3b5288816 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,17 +231,6 @@ 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 57fe62e6c5..08c2412856 100644
--- a/src/modules/evas/engines/gl_generic/evas_engine.c
+++ b/src/modules/evas/engines/gl_generic/evas_engine.c
@@ -2966,27 +2966,36 @@ _compute_gradient_coeffs(Span_Collector *sc,
     * regen in flight, or pointer realloc). */
    if (!(atlas && gd->color_table && gd->ctable_status == CTABLE_READY_DONE))
      {
-        sc->cached_ctable_ptr = NULL;  /* invalidate — content may regen */
+        /* Ramp not ready or atlas unavailable — invalidate gd-side cache so
+         * that when status returns to READY we recompute the CRC against
+         * potentially new content. */
+        gd->cached_ctable_crc_valid = EINA_FALSE;
         *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. */
+    * _upload_row in the atlas does the ARGB→RGBA swap at GL upload time.
+    *
+    * Tier-3 cache: skip the 4 KB CRC recompute on frames where color_table
+    * content has not changed.  Staleness signal: the framework cycles
+    * ctable_status through CTABLE_NOT_READY when stops change; we check
+    * the status at the time the CRC was last computed rather than the
+    * pointer (pointer may survive in-place regen).  Cache lives on gd so
+    * it survives Span_Collector slot reuse across shapes between frames. */
    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)
+   if (gd->cached_ctable_crc_valid &&
+       gd->cached_ctable_status == CTABLE_READY_DONE)
      {
-        version = sc->cached_ctable_crc;
+        version = gd->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;
+        gd->cached_ctable_crc       = version;
+        gd->cached_ctable_status    = gd->ctable_status;
+        gd->cached_ctable_crc_valid = EINA_TRUE;
      }
 
    int row = span_grad_atlas_lookup(atlas, sc->gradient_data,

-- 
To stop receiving notification emails like this one, please contact
the administrator of this repository.

Reply via email to