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 9003398032f79bc429aa094af578bc08d723747f
Author: Cedric BAIL <[email protected]>
AuthorDate: Fri Aug 14 11:39:48 2026 -0600

    perf(evas_ector_gl): render the VG pass outside the pipe
    
    A vector object's shapes are rasterised into that object's own FBO, but
    the draws went through a pipe entry, and a pipe entry is flushed against
    whatever surface the pipe is aimed at. Rendering one therefore meant
    evas_gl_common_context_target_surface_set() to the object's FBO and back
    to the canvas, and that call flushes. With one flush on the way in and
    another on the way out, per vector object, the pipe was never allowed to
    accumulate: every object's composite quad was drawn on its own.
    
    Collect the pass's quads and draw them directly instead. span_pass_draw()
    binds the target's framebuffer, clears the sub-rect, walks the quads in
    runs that share a program and bindings so a pass of many shapes costs one
    draw per distinct binding, and restores the framebuffer and viewport the
    pipe expects. The pipe's queued contents are never touched, so the canvas
    draws behind it keep accumulating.
    
    The draw core is now shared: span_shader_pipe_flush() became a thin
    wrapper over the same batch function, so the pipe path is unchanged for
    any caller that still uses it.
    
    One subtlety this moved: the NDC conversion read the target's dimensions
    back off gc->pipe[0].shader.surface, which worked only because
    target_surface_set had just pointed it at the FBO. With the pipe left
    alone it still names the canvas, and the quads came out scaled into a
    corner - visible as strokes drawn as small fragments and no fills at all.
    It now uses the target image directly.
    
    Test 125 gains 2.3% - 93.6 to 95.7 FPS as a mean over six interleaved
    runs. Less than the removed flushes suggest, because the composite quads
    still do not batch on that test: it resizes every object every frame, so
    each one's FBO surface lands in a different atlas slot and the draws have
    different textures. Content whose vector surfaces are stable already
    batches its composites, which is why the cached tests sit above 1000 FPS.
    
    The value beyond the 2% is that the pass is now a batch of quads with a
    target attached, which is the shape the remaining work needs: holding
    those batches across objects instead of drawing each immediately is what
    would let the 128 span draws merge and the 255 framebuffer binds go. That
    needs the span page to allocate per pass rather than being overwritten by
    the next object, which is a 2D allocator and a separate piece of work.
    
    All ten VG expedite tests pass at a 2/255 per-channel tolerance;
    ector-suite and evas-suite pass; the vgstride reproducer is clean under
    MALLOC_PERTURB_.
    
    Co-Authored-By: Claude Opus 5 (1M context) <[email protected]>
---
 .../evas/engines/gl_common/evas_gl_common.h        |  10 +-
 .../evas/engines/gl_common/evas_gl_context.c       |   6 +-
 .../evas/engines/gl_generic/evas_ector_gl_span.h   |  19 +++
 .../engines/gl_generic/evas_ector_gl_span_shader.c | 161 +++++++++++++++++++--
 src/modules/evas/engines/gl_generic/evas_engine.c  |  63 ++++----
 5 files changed, 213 insertions(+), 46 deletions(-)

diff --git a/src/modules/evas/engines/gl_common/evas_gl_common.h b/src/modules/evas/engines/gl_common/evas_gl_common.h
index 715249a8ef..12c067457c 100644
--- a/src/modules/evas/engines/gl_common/evas_gl_common.h
+++ b/src/modules/evas/engines/gl_common/evas_gl_common.h
@@ -298,7 +298,8 @@ typedef struct _Span_Channel_Params {
 
 /* Full parameter set for evas_gl_common_context_span_push(). */
 typedef struct _Span_Pipe_Params {
-   int      pool_w, pool_h;       /* pool texture dimensions */
+   int      pool_w, pool_h;
+       /* pool texture dimensions */
    int      max_spans;            /* max spans per row */
    int      x, y, w, h;          /* draw rect in canvas space */
    uint32_t mul_col;              /* multiply color */
@@ -672,6 +673,13 @@ void              evas_gl_common_context_rectangle_push(Evas_Engine_GL_Context *
 void              evas_gl_common_context_span_push(Evas_Engine_GL_Context *gc,
                                                    const Span_Pipe_Params *p,
                                                    const GLfloat ndc_quad[8]);
+/* Fill the six interleaved vertices of one span quad.  Exported so that the
+ * VG pass can build a batch without going through a pipe entry: it renders
+ * into its own FBO, so putting it in the pipe forced a target switch - and
+ * with it a flush - around every vector object. */
+void evas_gl_common_span_fill_vertices(void *out_buf, Span_Variant variant,
+                                       const Span_Pipe_Params *p,
+                                       const GLfloat ndc_quad[8]);
 void              evas_gl_common_context_image_push(Evas_Engine_GL_Context *gc,
                                                     Evas_GL_Texture *tex,
                                                     double sx, double sy, double sw, double sh,
diff --git a/src/modules/evas/engines/gl_common/evas_gl_context.c b/src/modules/evas/engines/gl_common/evas_gl_context.c
index 866bd057f9..ece5dd1ed2 100644
--- a/src/modules/evas/engines/gl_common/evas_gl_context.c
+++ b/src/modules/evas/engines/gl_common/evas_gl_context.c
@@ -2134,8 +2134,8 @@ _span_side_grad_set(GLfloat abc_y[4], GLfloat def[4], GLfloat radial[4],
    radial[3] = (GLfloat)side->grad_spread;
 }
 
-static void
-_span_fill_vertices(void *out_buf, Span_Variant variant,
+void
+evas_gl_common_span_fill_vertices(void *out_buf, Span_Variant variant,
                     const Span_Pipe_Params *p,
                     const GLfloat ndc_quad[8] /* TL,TR,BR,BL: x0y0,x1y0,x1y1,x0y1 */)
 {
@@ -2401,7 +2401,7 @@ evas_gl_common_context_span_push(Evas_Engine_GL_Context *gc,
         }
       void *write_ptr = (char *)gc->pipe[pn].array.span_vertex_data
                       + gc->pipe[pn].array.span_vertex_data_used;
-      _span_fill_vertices(write_ptr, variant, p, ndc_quad);
+      evas_gl_common_span_fill_vertices(write_ptr, variant, p, ndc_quad);
       gc->pipe[pn].array.span_vertex_data_used += 6 * vsize;
       gc->pipe[pn].array.span_variant            = variant;
    }
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 349dd287b9..62bd2e09c8 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
@@ -446,6 +446,25 @@ void span_shader_shutdown(void);
  */
 void span_shader_pipe_flush(Evas_Engine_GL_Context *gc, int pipe_idx);
 
+/**
+ * Render one VG object's span quads straight into @p target's framebuffer.
+ *
+ * Deliberately does not go through a pipe entry.  A pipe entry is flushed
+ * with whatever surface the pipe is targeting, so putting the VG pass there
+ * forced evas_gl_common_context_target_surface_set() around every vector
+ * object - and that flushes, which meant each object's composite quad was
+ * drawn on its own instead of batching with the rest of the canvas.
+ *
+ * Clears (@p clear_x, @p clear_y, @p clear_w, @p clear_h) in framebuffer
+ * coordinates first, then draws @p n quads, grouping consecutive ones that
+ * share a program and bindings into single draws.  The pipe's framebuffer
+ * and viewport are restored before returning; its queued contents are left
+ * untouched.
+ */
+void span_pass_draw(Evas_Engine_GL_Context *gc, Evas_GL_Image *target,
+                    const Span_Pipe_Params *quads, const GLfloat *ndc, int n,
+                    int clear_x, int clear_y, int clear_w, int clear_h);
+
 /**
  * Debug helper: read a single pixel from a GL texture via a temp FBO.
  * Logs the RGBA values with a caller-supplied label.  Throttled to avoid
diff --git a/src/modules/evas/engines/gl_generic/evas_ector_gl_span_shader.c b/src/modules/evas/engines/gl_generic/evas_ector_gl_span_shader.c
index 8fd5c05612..07bceb8d08 100644
--- a/src/modules/evas/engines/gl_generic/evas_ector_gl_span_shader.c
+++ b/src/modules/evas/engines/gl_generic/evas_ector_gl_span_shader.c
@@ -1584,19 +1584,30 @@ done:
 /* Evas pipe integration: span_shader_pipe_flush                       */
 /* ------------------------------------------------------------------ */
 
-void
-span_shader_pipe_flush(Evas_Engine_GL_Context *gc, int pipe_idx)
+/* Which program a quad needs.  Mirrors evas_gl_common_context_span_push();
+ * a plain colour rides in the gradient variant, so only a genuine gradient
+ * on either side selects it. */
+static Span_Variant
+_span_variant_of(const Span_Pipe_Params *p)
 {
-   Span_Variant  variant   = gc->pipe[pipe_idx].array.span_variant;
-   void         *vdata     = gc->pipe[pipe_idx].array.span_vertex_data;
-   int           nverts    = gc->pipe[pipe_idx].array.num;
-   GLsizei       stride    = (GLsizei)span_vertex_size(variant);
-   GLuint        fill_tex  = gc->pipe[pipe_idx].shader.span_fill_tex;
-   GLuint        stroke_tex= gc->pipe[pipe_idx].shader.span_stroke_tex;
-   GLuint        atlas_tex = gc->pipe[pipe_idx].shader.span_grad_atlas_tex;
-   GLuint        mask_tex  = gc->pipe[pipe_idx].shader.span_mask_tex;
-   float         inv_tw    = gc->pipe[pipe_idx].shader.span_inv_tw;
-   float         inv_th    = gc->pipe[pipe_idx].shader.span_inv_th;
+   int grad = ((p->fill.tex   && p->fill.type   >= SPAN_FILL_TYPE_GRADIENT_MIN) ||
+               (p->stroke.tex && p->stroke.type >= SPAN_FILL_TYPE_GRADIENT_MIN));
+
+   if (grad) return (p->mask_tex != 0) ? SPAN_VARIANT_GRADIENT_MASK
+                                       : SPAN_VARIANT_GRADIENT;
+   return (p->mask_tex != 0) ? SPAN_VARIANT_SOLID_MASK : SPAN_VARIANT_SOLID;
+}
+
+/* Issue one batch of span quads.  Shared by the pipe path and by the direct
+ * VG pass, which cannot use a pipe entry because it renders into its own
+ * framebuffer. */
+static void
+_span_draw_batch(Evas_Engine_GL_Context *gc, Span_Variant variant,
+                 const void *vdata, size_t vbytes, int nverts,
+                 GLuint fill_tex, GLuint stroke_tex, GLuint atlas_tex,
+                 GLuint mask_tex, float inv_tw, float inv_th)
+{
+   GLsizei       stride = (GLsizei)span_vertex_size(variant);
    GLuint        vao;
 
    /* Determine kind (0=solid, 1=gradient) and bind set from variant + textures. */
@@ -1661,10 +1672,7 @@ span_shader_pipe_flush(Evas_Engine_GL_Context *gc, int pipe_idx)
    if (vao) _gl_bind_vao(vao);
    glBindBuffer(GL_ARRAY_BUFFER, _span_vbo);
 
-   glBufferData(GL_ARRAY_BUFFER,
-                (GLsizeiptr)gc->pipe[pipe_idx].array.span_vertex_data_used,
-                vdata,
-                GL_STREAM_DRAW);
+   glBufferData(GL_ARRAY_BUFFER, (GLsizeiptr)vbytes, vdata, GL_STREAM_DRAW);
 
    if (!vao)
      {
@@ -1701,3 +1709,124 @@ span_shader_pipe_flush(Evas_Engine_GL_Context *gc, int pipe_idx)
    gc->state.current.cw         = 0;
    gc->state.current.ch         = 0;
 }
+
+void
+span_shader_pipe_flush(Evas_Engine_GL_Context *gc, int pipe_idx)
+{
+   _span_draw_batch(gc,
+                    gc->pipe[pipe_idx].array.span_variant,
+                    gc->pipe[pipe_idx].array.span_vertex_data,
+                    gc->pipe[pipe_idx].array.span_vertex_data_used,
+                    gc->pipe[pipe_idx].array.num,
+                    gc->pipe[pipe_idx].shader.span_fill_tex,
+                    gc->pipe[pipe_idx].shader.span_stroke_tex,
+                    gc->pipe[pipe_idx].shader.span_grad_atlas_tex,
+                    gc->pipe[pipe_idx].shader.span_mask_tex,
+                    gc->pipe[pipe_idx].shader.span_inv_tw,
+                    gc->pipe[pipe_idx].shader.span_inv_th);
+}
+
+/* ------------------------------------------------------------------ */
+/* Direct VG pass                                                      */
+/* ------------------------------------------------------------------ */
+
+/* Vertex scratch for the direct pass, grown on demand. */
+static void  *_span_pass_buf = NULL;
+static size_t _span_pass_sz  = 0;
+
+static void *
+_span_pass_buf_get(size_t need)
+{
+   if (need > _span_pass_sz)
+     {
+        void *p = realloc(_span_pass_buf, need);
+        if (!p) return NULL;
+        _span_pass_buf = p;
+        _span_pass_sz  = need;
+     }
+   return _span_pass_buf;
+}
+
+/* Restore the framebuffer and viewport the Evas pipe expects, given whatever
+ * surface it is currently targeting. */
+static void
+_span_pass_restore(Evas_Engine_GL_Context *gc)
+{
+   Evas_GL_Image *s = gc->pipe[0].shader.surface;
+
+   if (!s || s == gc->def_surface)
+     {
+        glsym_glBindFramebuffer(GL_FRAMEBUFFER, 0);
+        if ((gc->rot == 0) || (gc->rot == 180))
+          glViewport(0, 0, gc->w, gc->h);
+        else
+          glViewport(0, 0, gc->h, gc->w);
+     }
+   else
+     {
+        glsym_glBindFramebuffer(GL_FRAMEBUFFER, s->tex->pt->fb);
+        glViewport(s->tex->x, s->tex->y, s->w, s->h);
+     }
+}
+
+void
+span_pass_draw(Evas_Engine_GL_Context *gc, Evas_GL_Image *target,
+               const Span_Pipe_Params *quads, const GLfloat *ndc, int n,
+               int clear_x, int clear_y, int clear_w, int clear_h)
+{
+   int i, run_start;
+
+   if (!gc || !target || !target->tex || !target->tex->pt || n <= 0) return;
+   if (!span_shader_init()) return;
+
+   /* Bind directly rather than through evas_gl_common_context_target_surface_set:
+    * that flushes the pipe, and the pipe is holding the composite draws of
+    * every vector object rendered so far this frame.  Leaving them queued is
+    * the point - they can then batch into one draw instead of one each. */
+   glsym_glBindFramebuffer(GL_FRAMEBUFFER, target->tex->pt->fb);
+   glViewport(target->tex->x, target->tex->y, target->w, target->h);
+
+   glEnable(GL_SCISSOR_TEST);
+   glScissor(clear_x, clear_y, clear_w, clear_h);
+   glClearColor(0.0f, 0.0f, 0.0f, 0.0f);
+   glClear(GL_COLOR_BUFFER_BIT);
+   glDisable(GL_SCISSOR_TEST);
+
+   /* Walk the quads in runs that share a program and its bindings, so a pass
+    * of many shapes costs one draw per distinct binding rather than one per
+    * shape. */
+   run_start = 0;
+   while (run_start < n)
+     {
+        Span_Variant variant = _span_variant_of(&quads[run_start]);
+        size_t vsize, need;
+        void *buf;
+        int end = run_start + 1, k;
+
+        while (end < n &&
+               _span_variant_of(&quads[end]) == variant &&
+               quads[end].fill.tex        == quads[run_start].fill.tex &&
+               quads[end].stroke.tex      == quads[run_start].stroke.tex &&
+               quads[end].grad_atlas_tex  == quads[run_start].grad_atlas_tex &&
+               quads[end].mask_tex        == quads[run_start].mask_tex)
+          end++;
+
+        vsize = span_vertex_size(variant);
+        need  = vsize * 6 * (size_t)(end - run_start);
+        buf   = _span_pass_buf_get(need);
+        if (!buf) break;
+
+        for (k = run_start; k < end; k++)
+          evas_gl_common_span_fill_vertices((char *)buf + vsize * 6 * (size_t)(k - run_start),
+                                            variant, &quads[k], ndc + k * 8);
+
+        _span_draw_batch(gc, variant, buf, need, 6 * (end - run_start),
+                         quads[run_start].fill.tex, quads[run_start].stroke.tex,
+                         quads[run_start].grad_atlas_tex, quads[run_start].mask_tex,
+                         1.0f / (float)quads[run_start].pool_w,
+                         1.0f / (float)quads[run_start].pool_h);
+        run_start = end;
+     }
+
+   _span_pass_restore(gc);
+}
diff --git a/src/modules/evas/engines/gl_generic/evas_engine.c b/src/modules/evas/engines/gl_generic/evas_engine.c
index dc770a3537..59b727c65c 100644
--- a/src/modules/evas/engines/gl_generic/evas_engine.c
+++ b/src/modules/evas/engines/gl_generic/evas_engine.c
@@ -3244,7 +3244,14 @@ eng_ector_end(void *engine,
                                       stroke_arr, stroke_count))
                   goto span_done;
 
-                evas_gl_common_context_target_surface_set(gc, glim);
+                /* Collect this pass's quads and draw them in one go below,
+                 * without switching the pipe's target surface.  Switching it
+                 * flushes, and the pipe is holding the composite quads of
+                 * every vector object drawn so far this frame; leaving them
+                 * queued lets them batch. */
+                Span_Pipe_Params *_pass_q = NULL;
+                GLfloat          *_pass_ndc = NULL;
+                int               _pass_n = 0, _pass_alloc = 0;
 
                 /* Bump gradient atlas LRU frame counter for this render pass. */
                 span_grad_atlas_frame_begin(((Render_Engine_GL_Generic *)engine)->grad_atlas);
@@ -3271,19 +3278,8 @@ eng_ector_end(void *engine,
                  *
                  * The Evas GL state cache is invalidated on textures and
                  * render op below so subsequent draws restore them as needed. */
-                evas_gl_common_context_flush(gc);
-                glEnable(GL_SCISSOR_TEST);
-                glScissor(ox, oy, w, h);
-                glClearColor(0.0f, 0.0f, 0.0f, 0.0f);
-                glClear(GL_COLOR_BUFFER_BIT);
-                glDisable(GL_SCISSOR_TEST);
-                gc->state.current.clip      = 0;
-                gc->state.current.cx        = 0;
-                gc->state.current.cy        = 0;
-                gc->state.current.cw        = 0;
-                gc->state.current.ch        = 0;
-                gc->state.current.cur_tex   = -1;
-                gc->state.current.render_op = -1;
+                /* The clear happens inside span_pass_draw, with the
+                 * target bound. */
 
                 /* Per-shape draw loop.
                  *
@@ -3506,17 +3502,14 @@ eng_ector_end(void *engine,
                                  * window dims here would compress all geometry
                                  * into a corner of the actual sub-rect.
                                  *
-                                 * Mirrors what shader_array_flush computes for
-                                 * non-span pipes: surface->w/h for FBO,
-                                 * gc->w/h for the default surface. */
+                                 * The target is glim, this pass's own FBO
+                                 * image.  It cannot be read back off the pipe:
+                                 * the pipe is still aimed at the canvas,
+                                 * because this pass deliberately does not
+                                 * switch it. */
                                 GLfloat _ndc[8];
-                                Evas_GL_Image *_tgt = gc->pipe[0].shader.surface;
-                                float _gw, _gh;
-                                if (_tgt && _tgt != gc->def_surface)
-                                  { _gw = (float)_tgt->w; _gh = (float)_tgt->h; }
-                                else
-                                  { _gw = (float)(gc->w ? gc->w : 1);
-                                    _gh = (float)(gc->h ? gc->h : 1); }
+                                float _gw = (float)(glim->w ? glim->w : 1);
+                                float _gh = (float)(glim->h ? glim->h : 1);
                                 float _x0 = (float)_spp.x;
                                 float _y0 = (float)_spp.y;
                                 float _x1 = _x0 + (float)_spp.w;
@@ -3525,13 +3518,31 @@ eng_ector_end(void *engine,
                                 _ndc[2] = _x1 / _gw * 2.0f - 1.0f; _ndc[3] = _y0 / _gh * 2.0f - 1.0f; /* TR */
                                 _ndc[4] = _x1 / _gw * 2.0f - 1.0f; _ndc[5] = _y1 / _gh * 2.0f - 1.0f; /* BR */
                                 _ndc[6] = _x0 / _gw * 2.0f - 1.0f; _ndc[7] = _y1 / _gh * 2.0f - 1.0f; /* BL */
-                                evas_gl_common_context_span_push(gc, &_spp, _ndc);
+                                if (_pass_n == _pass_alloc)
+                                  {
+                                     int na = _pass_alloc ? _pass_alloc * 2 : 8;
+                                     Span_Pipe_Params *nq =
+                                        realloc(_pass_q, (size_t)na * sizeof(*nq));
+                                     GLfloat *nn =
+                                        realloc(_pass_ndc, (size_t)na * 8 * sizeof(*nn));
+                                     if (nq) _pass_q = nq;
+                                     if (nn) _pass_ndc = nn;
+                                     if (!nq || !nn) continue;
+                                     _pass_alloc = na;
+                                  }
+                                _pass_q[_pass_n] = _spp;
+                                memcpy(_pass_ndc + _pass_n * 8, _ndc, sizeof(_ndc));
+                                _pass_n++;
                              }
                           }
                      }
                   } /* per-shape loop */
 
-                evas_gl_common_context_target_surface_set(gc, gc->def_surface);
+                if (_pass_n > 0)
+                  span_pass_draw(gc, glim, _pass_q, _pass_ndc, _pass_n,
+                                 ox, oy, w, h);
+                free(_pass_q);
+                free(_pass_ndc);
              }
         }
 span_done:

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

Reply via email to