This is an automated email from the git hooks/post-receive script.

git pushed a commit to branch span-gl-clean
in repository efl.

View the commit online.

commit b7e0636af2b7e2e492be8686993cb4abd8c1d0a6
Author: Cedric BAIL <[email protected]>
AuthorDate: Fri Aug 14 10:53:47 2026 -0600

    fix(evas_ector_gl): stop the ector surface owning the raster buffer
    
    The span path pointed the shared ector surface at a buffer it asked the
    surface to allocate:
    
        ector_buffer_pixels_set(ector, NULL, w, h, 0, ARGB8888, EINA_TRUE);
    
    A NULL pixel pointer makes ector_buffer_pixels_set() calloc the storage
    and clear nofree, so the surface owns it. That is fine until something
    swaps the surface's buffer, and _evas_vg_render() does exactly that: a
    container with alpha below 255 takes the blend-buffer path, which saves
    the current pixels, points the surface at its own buffer, renders the
    children, and then restores what it saved. The swap runs
    
        if (pd->pixels.u8 && (pd->pixels.u8 != pixels))
          _ector_software_buffer_base_pixels_clear(obj, pd);   // frees, !nofree
    
    so the engine's buffer is freed, and the restore reinstates the pointer
    it saved a moment earlier - now dangling. Every later write through it
    is a use-after-free, and the process segfaults inside
    ector_surface_draw_image() a frame or two later.
    
    Master is not affected: its eng_ector_begin hands over the image's own
    pixels, so nofree is set and nothing is ever freed. This came in with
    the span path.
    
    Sizing was wrong in the same place. The buffer was allocated as
    stride * h, where stride belongs to whichever object was rendered first
    on that canvas - every vector object shares one ector surface - while
    generic->w was overwritten with the current object's width. The buffer
    was therefore as many pixels wide as the first object while telling
    everyone else it was as wide as they are. With a 28px object rendered
    before a 517px one, ector_surface_draw_image() wrote 1069156 bytes into
    57904 bytes of allocation.
    
    Own the buffer in the engine instead. It lives on the ector surface
    data, is sized from each object's own width, grows on a high-water
    mark, and is handed to pixels_set() as a plain pointer so the surface
    never takes ownership and the blend path's swap cannot free it. It is
    released in eng_ector_destroy().
    
    Reproducer, needing only public API and both bugs on the way through:
    
        a 32x32 vector object, rendered first, to fix the stride;
        a 512x512 one whose root is an EFL_CANVAS_VG_CONTAINER with
        efl_gfx_color_set(root, 128, 128, 128, 128).
    
    On gl_x11 that segfaults within a few frames before this change and
    survives after it. Both objects are ordinary API use; nothing here
    needs an unusual engine or a private call.
    
    Left for a separate change: on the span path that container's alpha is
    also ignored. The blend path composites the group into the CPU raster
    buffer, which the GL path never reads, so the group renders fully
    opaque where the software engine renders it at alpha 128. The work is
    done and then discarded.
    
    Expedite VG tests are unaffected in output and speed (test 125 at 95.8
    FPS, test 121 at 1569). All ten render byte-identical frames;
    ector-suite and evas-suite pass.
    
    Co-Authored-By: Claude Opus 5 (1M context) <[email protected]>
---
 src/lib/ector/software/ector_software_private.h   | 11 +++-
 src/modules/evas/engines/gl_generic/evas_engine.c | 73 ++++++++++-------------
 2 files changed, 40 insertions(+), 44 deletions(-)

diff --git a/src/lib/ector/software/ector_software_private.h b/src/lib/ector/software/ector_software_private.h
index 9700f6c470..96289ae5a5 100644
--- a/src/lib/ector/software/ector_software_private.h
+++ b/src/lib/ector/software/ector_software_private.h
@@ -160,9 +160,14 @@ struct _Ector_Software_Surface_Data
    int    span_collectors_stroke_count;
    int    span_collectors_stroke_alloc;
 
-   /* Bytes currently allocated for the shared raster pixel buffer.  The
-    * buffer's own descriptor only records the height in use, which is not
-    * the height it was allocated at, so growth has to be tracked here. */
+   /* Scratch raster buffer for the GL span path, owned here rather than by
+    * the ector buffer.  Handing it to ector_buffer_pixels_set() as a plain
+    * pointer sets nofree, which matters because the VG blend path swaps the
+    * surface's buffer out and back again: were the buffer to own the
+    * allocation, that swap would free it and the restore would reinstate a
+    * dangling pointer.  The buffer descriptor also only records the height
+    * in use, not the height allocated, so growth is tracked here too. */
+   void  *span_pixels;
    size_t span_pixels_alloc;
 
    /* GL composite mask for the current eng_ector_begin/end window.
diff --git a/src/modules/evas/engines/gl_generic/evas_engine.c b/src/modules/evas/engines/gl_generic/evas_engine.c
index f86cbf40f4..b1db92b579 100644
--- a/src/modules/evas/engines/gl_generic/evas_engine.c
+++ b/src/modules/evas/engines/gl_generic/evas_engine.c
@@ -2530,6 +2530,10 @@ eng_ector_destroy(void *engine EINA_UNUSED, Ector_Surface *ector)
                   Span_Collector *sc = (Span_Collector *)pd->span_collectors_stroke[ci];
                   if (sc) span_collector_free(sc);
                }
+             free(pd->span_pixels);
+             pd->span_pixels       = NULL;
+             pd->span_pixels_alloc = 0;
+
              free(pd->span_collectors_stroke);
              pd->span_collectors_stroke       = NULL;
              pd->span_collectors_stroke_count  = 0;
@@ -2821,54 +2825,41 @@ eng_ector_begin(void *engine, void *surface,
         eng_image_size_get(engine, glim, &w, &h);
         if (w <= 0 || h <= 0) return EINA_FALSE;
 
-        /* Set ector surface dimensions for rasterizer clipping.
-         *
-         * On first call, pixels_set initialises struct fields (stride,
-         * pixel_size, cspace).  After that, just update w/h and grow
-         * the pixel buffer with realloc if needed (high-water mark)
-         * to avoid the per-frame free+calloc cycle in pixels_set. */
+        /* Point the ector surface at a scratch buffer big enough for this
+         * object, for the rasterizer's clipping bounds. */
         {
            Ector_Software_Surface_Data *spd =
               efl_data_scope_get(ector, ECTOR_SOFTWARE_SURFACE_CLASS);
            Ector_Software_Buffer_Base_Data *bbd =
               efl_data_scope_get(ector, ECTOR_SOFTWARE_BUFFER_BASE_MIXIN);
-           if (!bbd || !bbd->pixels.u8)
-             {
-                ector_buffer_pixels_set(ector, NULL, w, h, 0,
-                                        EFL_GFX_COLORSPACE_ARGB8888, EINA_TRUE);
-                if (spd)
-                  {
-                     Ector_Software_Buffer_Base_Data *nbd =
-                        efl_data_scope_get(ector, ECTOR_SOFTWARE_BUFFER_BASE_MIXIN);
-                     spd->span_pixels_alloc = nbd ? (size_t)nbd->stride * h : 0;
-                  }
-             }
-           else if (bbd->generic && (bbd->generic->w != (unsigned)w || bbd->generic->h != (unsigned)h))
-             {
-                size_t needed = (size_t)bbd->stride * h;
-                size_t have   = spd ? spd->span_pixels_alloc
-                                    : (size_t)bbd->stride * bbd->generic->h;
+           /* Size from this object's own width.  Every vector object on the
+            * canvas shares one ector surface, so sizing from the stride the
+            * buffer happens to carry made it as many pixels wide as whichever
+            * object was rendered first while telling everyone else it was as
+            * wide as they are. */
+           size_t row    = (size_t)w * 4;
+           size_t needed = row * (size_t)h;
 
-                /* Grow on a high-water mark.  generic->h is the height in
-                 * use, not the height allocated, so deriving the current
-                 * allocation from it makes every shrink-then-grow realloc a
-                 * buffer that was already large enough.  With one surface
-                 * shared by every vector object on the canvas and their
-                 * sizes changing independently, that was reallocating and
-                 * zeroing hundreds of kilobytes many times a frame. */
-                if (needed > have)
-                  {
-                     uint8_t *p = realloc(bbd->pixels.u8, needed);
-                     if (p)
-                       {
-                          bbd->pixels.u8 = p;
-                          memset(p + have, 0, needed - have);
-                          if (spd) spd->span_pixels_alloc = needed;
-                       }
-                  }
-                bbd->generic->w = w;
-                bbd->generic->h = h;
+           if (!spd) return EINA_FALSE;
+
+           /* Grow on a high-water mark; generic->h is the height in use, not
+            * the height allocated, so it cannot answer this question. */
+           if (needed > spd->span_pixels_alloc)
+             {
+                void *p = realloc(spd->span_pixels, needed);
+                if (!p) return EINA_FALSE;
+                memset((uint8_t *)p + spd->span_pixels_alloc, 0,
+                       needed - spd->span_pixels_alloc);
+                spd->span_pixels       = p;
+                spd->span_pixels_alloc = needed;
              }
+
+           /* Hand the buffer in as a pointer so the surface never owns it. */
+           if (!bbd || bbd->pixels.u8 != spd->span_pixels ||
+               !bbd->generic ||
+               bbd->generic->w != (unsigned)w || bbd->generic->h != (unsigned)h)
+             ector_buffer_pixels_set(ector, spd->span_pixels, w, h, (int)row,
+                                     EFL_GFX_COLORSPACE_ARGB8888, EINA_TRUE);
         }
 
         /* Per-shape collector model.

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

Reply via email to