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 ae6952f8ee3c8828c77d8bd56614197cac8871ed
Author: Cedric BAIL <[email protected]>
AuthorDate: Fri Aug 14 15:05:00 2026 -0600

    evas: stop clearing span row tails nothing reads
    
    _flush_row_tail() memset the whole remainder of a row - up to 260 bytes
    - every time the collector moved to a new row. On a 600x300 drawing with
    five shapes that is a few hundred kilobytes of strided writes a frame,
    spent erasing bytes no consumer looks at: the shader's scan stops at the
    first zero length, and the spatial-split walkers iterate exactly
    span_counts[y] entries. Only the terminator has to be written.
    
    The inner write loop also read back the four bytes it had just stored to
    feed the rolling hash, making the hash wait on those stores. Compose the
    value once and hash that instead; the bytes written are unchanged.
    
    The unit test covering this asserted the whole tail was zero, which
    described the old implementation rather than the requirement. It now
    asserts what actually has to hold - the terminator - and, deliberately,
    that the bytes past it are still stale, so the weaker invariant is
    written down rather than left implied.
    
    Worth being clear about the size of this: test 125 gains 1.0%, four
    samples to four, and test 122 is unchanged. It is kept because it does
    strictly less work and removes memory traffic that costs more on the
    memory-bound devices this path also targets than it does on a desktop
    Intel part. It is not the answer for test 122.
    
    All ten VG expedite tests pass at a 2/255 per-channel tolerance;
    ector-suite and evas-suite pass.
    
    Co-Authored-By: Claude Opus 5 (1M context) <[email protected]>
---
 .../evas/engines/gl_generic/evas_ector_gl_span.c   | 30 ++++++++++++++------
 src/tests/ector/suite/ector_test_span_collector.c  | 32 +++++++++++++++-------
 2 files changed, 44 insertions(+), 18 deletions(-)

diff --git a/src/modules/evas/engines/gl_generic/evas_ector_gl_span.c b/src/modules/evas/engines/gl_generic/evas_ector_gl_span.c
index f019d3908b..9713b05c93 100644
--- a/src/modules/evas/engines/gl_generic/evas_ector_gl_span.c
+++ b/src/modules/evas/engines/gl_generic/evas_ector_gl_span.c
@@ -658,9 +658,14 @@ _flush_row_tail(Span_Collector *sc, int ti, int y)
    Span_Texture *tex = &sc->textures[ti];
    int           idx = tex->span_counts[y];
 
+   /* Only the terminator has to be written, not the whole tail.  Nothing
+    * reads past it: the shader's scan breaks at the first zero length, and
+    * the split helpers walk exactly span_counts[y] entries.  Clearing the
+    * rest was up to 260 bytes of strided memset for every row of every
+    * shape - on a 600x300 drawing with five shapes, a few hundred kilobytes
+    * a frame to erase bytes no one looks at. */
    if (idx < sc->max_spans)
-     memset(tex->buffer + ((size_t)y * sc->stride) + ((size_t)idx * 4),
-            0, (size_t)(sc->max_spans + 1 - idx) * 4);
+     tex->buffer[((size_t)y * sc->stride) + ((size_t)idx * 4) + 1] = 0;
 }
 
 /* ------------------------------------------------------------------ */
@@ -806,12 +811,21 @@ _collect_spans_solid(int count, const SW_FT_Span *spans, void *user_data)
                 int g = (cur_x == sx) ? gap : 0;
                 if (g > 255) g = 255;
 
-                entry = tex->buffer + ((size_t)y * sc->stride) + ((size_t)idx * 4);
-                entry[0] = (uint8_t)cov;   /* byte0 → B in BGRA */
-                entry[1] = (uint8_t)chunk;  /* byte1 → G in BGRA */
-                entry[2] = (uint8_t)g;      /* byte2 → R in BGRA */
-                entry[3] = 0;               /* byte3 → A in BGRA */
-                tex->rolling_hash = tex->rolling_hash * 31 + *((const uint32_t *)entry);
+                /* Compose once, then hash the value rather than reading
+                 * back the bytes just stored - that read waits on the
+                 * stores in the innermost loop of the collector. */
+                {
+                   uint32_t v = (uint32_t)cov
+                              | ((uint32_t)chunk << 8)
+                              | ((uint32_t)g     << 16);
+
+                   entry = tex->buffer + ((size_t)y * sc->stride) + ((size_t)idx * 4);
+                   entry[0] = (uint8_t)cov;    /* byte0 → B in BGRA */
+                   entry[1] = (uint8_t)chunk;  /* byte1 → G in BGRA */
+                   entry[2] = (uint8_t)g;      /* byte2 → R in BGRA */
+                   entry[3] = 0;               /* byte3 → A in BGRA */
+                   tex->rolling_hash = tex->rolling_hash * 31 + v;
+                }
 
                 cur_x += chunk;
                 remaining -= chunk;
diff --git a/src/tests/ector/suite/ector_test_span_collector.c b/src/tests/ector/suite/ector_test_span_collector.c
index 69c8262ec0..6c76df9e58 100644
--- a/src/tests/ector/suite/ector_test_span_collector.c
+++ b/src/tests/ector/suite/ector_test_span_collector.c
@@ -724,18 +724,18 @@ EFL_END_TEST
  * are zero.  This confirms the memset covers the full tail rather than
  * writing only a single sentinel byte.
  */
-EFL_START_TEST(span_collector_solid_tail_memset)
+EFL_START_TEST(span_collector_solid_row_terminator)
 {
    Span_Collector *sc;
    Span_Data       sd;
    SW_FT_Span      span;
    uint8_t        *row;
-   int             tail_start, tail_bytes, b;
 
    sc = span_collector_new(50, 16, Solid);
    ck_assert_ptr_nonnull(sc);
 
-   /* Pollute the buffer with non-zero bytes to simulate stale data. */
+   /* Pollute the buffer with non-zero bytes to simulate stale data left by
+    * an earlier frame. */
    memset(sc->textures[0].buffer, 0xAB, (size_t)50 * sc->stride);
 
    _sd_init_solid(&sd, sc, 0xFFFFFFFF);
@@ -748,13 +748,25 @@ EFL_START_TEST(span_collector_solid_tail_memset)
 
    ck_assert_int_eq(sc->textures[0].span_counts[7], 1);
 
-   /* Tail starts at entry 1 (one past the span) and runs to max_spans+1. */
-   row        = sc->textures[0].buffer + (7 * sc->stride);
-   tail_start = 1 * 4;                            /* first tail entry */
-   tail_bytes = (sc->max_spans + 1 - 1) * 4;      /* sentinel slot included */
+   row = sc->textures[0].buffer + (7 * sc->stride);
 
-   for (b = 0; b < tail_bytes; b++)
-     ck_assert_int_eq(row[tail_start + b], 0);
+   /* The span itself. */
+   ck_assert_int_eq(row[0], 200);   /* coverage */
+   ck_assert_int_eq(row[1], 20);    /* length   */
+
+   /* What has to hold is that the row is terminated: the entry one past the
+    * last span carries a zero length, which is what stops every consumer -
+    * the shader's scan loop and the spatial-split walkers alike.
+    *
+    * The rest of the tail is deliberately left as it was.  Clearing it cost
+    * a strided memset of the full row for every row of every shape, and no
+    * consumer ever reads past the terminator, so those bytes only had to be
+    * erased to satisfy a test. */
+   ck_assert_int_eq(row[1 * 4 + 1], 0);
+
+   /* And the stale bytes beyond it are indeed still stale, which is the
+    * point: this documents the weaker invariant rather than hiding it. */
+   ck_assert_int_eq(row[2 * 4 + 1], 0xAB);
 
    span_collector_free(sc);
 }
@@ -825,6 +837,6 @@ ector_test_span_collector(TCase *tc)
    tcase_add_test(tc, span_collector_post_split_routing);
    tcase_add_test(tc, span_collector_gradient_basic);
    tcase_add_test(tc, span_collector_solid_overflow_drop);
-   tcase_add_test(tc, span_collector_solid_tail_memset);
+   tcase_add_test(tc, span_collector_solid_row_terminator);
    tcase_add_test(tc, span_collector_clear_stale_sentinel);
 }

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

Reply via email to