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 c0f9cb62b8f789ec52200e03a00596d0202510d1
Author: [email protected] <[email protected]>
AuthorDate: Mon Aug 10 20:48:59 2026 -0600

    fix(evas_ector_gl): fall back to CPU rasterization when spans are unusable
    
    eng_ector_begin installed the span collector callbacks unconditionally, so
    on a device where the span shaders cannot run, ector emitted spans that
    nothing consumed while eng_ector_end cleared the FBO sub-rect regardless.
    The visible result was vector graphics rendering fully transparent, with no
    fallback of any kind.
    
    Leave the callbacks NULL when span_path_usable() says no. The software
    rasterizer then writes into the CPU buffer eng_ector_begin already
    allocates, and eng_ector_end uploads it - the path the engine used before
    the span renderer existed.
    
    That upload path had actually been deleted by commit a18c0d92a4 ("refactor:
    remove software fallback from GL engine"), which made the FBO-backed span
    surface the only surface type eng_ector_surface_create ever produced.
    Leaving the collectors NULL alone was not enough: the software rasterizer's
    default pixel buffer lives inside the ector object, disconnected from that
    FBO texture, so nothing ever reached the screen (verified: 0 non-white
    pixels with EVAS_GL_SPAN_TIER=off before this fix). Restored the
    CPU-backed surface/begin/end paths that a18c0d92a4 removed, now gated on
    span_path_usable() instead of the old use_span_buffer/use_gl flags, in
    eng_ector_surface_create, eng_ector_mask_surface_create, eng_ector_begin
    and eng_ector_end.
    
    Verified with EVAS_GL_SPAN_TIER=off: vector tests 117 through 122 render and
    match the software backend within tolerance, and test 119 matches the
    span-enabled render pixel-for-pixel on non-white pixel count (67694).  Also
    verified a genuine shader-compile failure (not just the tier override)
    reaches the same fallback.
    
    Co-Authored-By: Claude Opus 5 (1M context) <[email protected]>
---
 src/modules/evas/engines/gl_generic/evas_engine.c | 113 ++++++++++++++++++----
 1 file changed, 92 insertions(+), 21 deletions(-)

diff --git a/src/modules/evas/engines/gl_generic/evas_engine.c b/src/modules/evas/engines/gl_generic/evas_engine.c
index bf85e6c68e..63a022c471 100644
--- a/src/modules/evas/engines/gl_generic/evas_engine.c
+++ b/src/modules/evas/engines/gl_generic/evas_engine.c
@@ -2601,17 +2601,31 @@ eng_ector_surface_create(void *engine, int width, int height, int *error)
 
    *error = EINA_FALSE;
 
-   /* FBO-backed render surface.  The span shader draws directly into the
-    * texture attached to this image's FBO.  Use the atlas pool so multiple
-    * VG objects share a single GL FBO texture, eliminating per-frame
-    * glGenFramebuffers/glDeleteFramebuffers overhead when VG object sizes
-    * change.  The image's tex->x/y carry the atlas sub-region offset. */
-   {
-      Evas_Engine_GL_Context *ctx = gl_generic_context_find(engine, EINA_TRUE);
-      surface = evas_gl_common_image_surface_noscale_new(ctx, width, height, EINA_TRUE);
-      if (!surface)
-        *error = EINA_TRUE;
-   }
+   if (span_path_usable())
+     {
+        /* FBO-backed render surface.  The span shader draws directly into the
+         * texture attached to this image's FBO.  Use the atlas pool so multiple
+         * VG objects share a single GL FBO texture, eliminating per-frame
+         * glGenFramebuffers/glDeleteFramebuffers overhead when VG object sizes
+         * change.  The image's tex->x/y carry the atlas sub-region offset. */
+        Evas_Engine_GL_Context *ctx = gl_generic_context_find(engine, EINA_TRUE);
+        surface = evas_gl_common_image_surface_noscale_new(ctx, width, height, EINA_TRUE);
+        if (!surface)
+          *error = EINA_TRUE;
+     }
+   else
+     {
+        /* Span path unusable: fall back to a plain CPU-backed image.  The
+         * software rasterizer draws into it directly (no span collectors
+         * installed, see eng_ector_begin) and eng_ector_end uploads the
+         * result to the GPU with eng_image_data_put. */
+        surface = eng_image_new_from_copied_data(engine, width, height, NULL,
+                                                  EINA_TRUE, EVAS_COLORSPACE_ARGB8888);
+        if (!surface)
+          *error = EINA_TRUE;
+        else /* Hint for zero-copy texture upload. */
+          eng_image_content_hint_set(engine, surface, EVAS_IMAGE_CONTENT_HINT_DYNAMIC);
+     }
 
    return surface;
 }
@@ -2621,16 +2635,22 @@ eng_ector_mask_surface_create(void *engine, int width, int height, int *error)
 {
    *error = EINA_FALSE;
 
-   /* Mask FBO must use a dedicated texture — not shared with the atlas.
-    * The main VG FBO and the mask FBO would otherwise map to the same GL
-    * texture object, creating a read/write feedback loop when the span
-    * shader samples the mask while rendering into the main FBO. */
-   {
-      Evas_Engine_GL_Context *ctx = gl_generic_context_find(engine, EINA_TRUE);
-      void *surface = evas_gl_common_image_surface_noscale_noatlas_new(ctx, width, height, EINA_TRUE);
-      if (!surface) *error = EINA_TRUE;
-      return surface;
-   }
+   if (span_path_usable())
+     {
+        /* Mask FBO must use a dedicated texture — not shared with the atlas.
+         * The main VG FBO and the mask FBO would otherwise map to the same GL
+         * texture object, creating a read/write feedback loop when the span
+         * shader samples the mask while rendering into the main FBO. */
+        Evas_Engine_GL_Context *ctx = gl_generic_context_find(engine, EINA_TRUE);
+        void *surface = evas_gl_common_image_surface_noscale_noatlas_new(ctx, width, height, EINA_TRUE);
+        if (!surface) *error = EINA_TRUE;
+        return surface;
+     }
+   else
+     {
+        /* CPU fallback: no atlas concern, delegate to the regular path. */
+        return eng_ector_surface_create(engine, width, height, error);
+     }
 }
 
 static void
@@ -2744,6 +2764,30 @@ eng_ector_begin(void *engine, void *surface,
                 void *context EINA_UNUSED, Ector_Surface *ector,
                 int x, int y, Eina_Bool do_async EINA_UNUSED)
 {
+   if (!span_path_usable())
+     {
+        /* CPU fallback: surface is a plain image (see eng_ector_surface_create).
+         * Draw directly into the image's own pixel buffer with the software
+         * rasterizer, exactly as the engine did before the span path existed.
+         * No span collectors are involved, so ector must write into pixels
+         * that eng_ector_end can hand straight back to the GPU. */
+        Evas_GL_Image *glim = surface;
+        DATA32 *pixels;
+        int w, h, stride, load_err;
+
+        glim = eng_image_data_get(engine, glim, EINA_TRUE, &pixels, &load_err, NULL);
+        if (!glim || !pixels) return EINA_FALSE;
+        eng_image_stride_get(engine, glim, &stride);
+        eng_image_size_get(engine, glim, &w, &h);
+
+        memset(pixels, 0, stride * h);
+
+        ector_buffer_pixels_set(ector, pixels, w, h, stride,
+                                EFL_GFX_COLORSPACE_ARGB8888, EINA_TRUE);
+        ector_surface_reference_point_set(ector, x, y);
+        return EINA_TRUE;
+     }
+
    {
       Evas_GL_Image *glim = surface;
       int w, h;
@@ -2809,6 +2853,12 @@ eng_ector_begin(void *engine, void *surface,
                 Span_Data *sd = &pd->rasterizer->fill_data;
                 sd->span_collector               = NULL;
                 sd->span_is_stroke               = EINA_FALSE;
+
+                /* Reaching here means span_path_usable() already returned
+                 * EINA_TRUE (checked at function entry above) — the
+                 * !span_path_usable() case returns early with the CPU
+                 * fallback and never installs collectors, so ector does not
+                 * emit spans that nothing consumes. */
                 sd->collector_solid              = _collect_spans_solid;
                 sd->collector_gradient           = _collect_spans_gradient;
                 sd->collector_composite          = _collect_spans_composite;
@@ -3064,6 +3114,27 @@ eng_ector_end(void *engine,
               Ector_Surface *ector,
               Eina_Bool do_async EINA_UNUSED)
 {
+   if (!span_path_usable())
+     {
+        /* CPU fallback: the software rasterizer wrote directly into the
+         * image's own pixel buffer (set up in eng_ector_begin).  Push it
+         * to the GPU the same way the engine did before the span path
+         * existed.  The double eng_image_data_put() call mirrors that
+         * pre-span code: the first marks the image dirty, the second
+         * uploads it. */
+        Evas_GL_Image *glim = surface;
+        DATA32 *pixels;
+        int load_err;
+
+        glim = eng_image_data_get(engine, glim, EINA_FALSE, &pixels, &load_err, NULL);
+
+        eng_image_data_put(engine, glim, pixels);
+        eng_image_data_put(engine, glim, pixels);
+        ector_buffer_pixels_set(ector, NULL, 0, 0, 0, EFL_GFX_COLORSPACE_ARGB8888, EINA_TRUE);
+        evas_common_cpu_end_opt();
+        return;
+     }
+
    {
       Ector_Software_Surface_Data *espd = efl_data_scope_get(ector, ECTOR_SOFTWARE_SURFACE_CLASS);
       Evas_GL_Image *glim = surface;

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

Reply via email to