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 66d3ad60724377f242a1385d3ab56d3ad2547455
Author: Cedric BAIL <[email protected]>
AuthorDate: Fri Aug 14 11:24:55 2026 -0600

    perf(evas_ector_gl): draw a mixed fill and stroke in one pass
    
    A shape with a gradient fill and a plain-coloured stroke - a rounded
    rect with a border, as ordinary as vector content gets - was being
    pushed twice, because the gradient and solid main bodies are separate
    programs and a pipe entry carries one program. That doubled everything
    per shape: two draws, two program binds, two vertex uploads, two sets
    of texture binds.
    
    Let a plain colour ride in the gradient variant instead. A side whose
    fill type is not a gradient goes out as SPAN_GRAD_TYPE_SOLID with its
    premultiplied colour in the four components of grad_abc_y - the slots a
    linear gradient uses for its coefficients and ramp row - and the shader
    takes that verbatim rather than sampling the ramp atlas. Nothing new
    is added to the vertex layout, so the attribute count, and with it the
    device requirement the span path is gated on, is unchanged.
    
    Per frame on expedite test 125, 128 vector objects each with a gradient
    fill and a solid stroke:
    
        glDrawArrays        383 -> 256
        glUseProgram        383 -> 256
        glBufferData        255 -> 128
        glBindTexture      1023 -> 768
        glActiveTexture    1021 -> 639
        glUniform1i/1f      638 -> 384
    
    Test 125 gains 1.9% - 92.4 to 94.1 FPS, every sample of four above
    every sample of the baseline. Less than the call counts suggest,
    because the fragment work is unchanged: the merged draw runs both scans
    in one fragment where the split ran one scan each over the same quad.
    The rest of the suite is within noise.
    
    Output moves by up to 2/255 on the tests with gradient fills, and in
    the right direction. The split composited the stroke against a fill
    that had already been quantised into the 8-bit framebuffer; combining
    them in the shader keeps the intermediate in float and quantises once.
    It is also what the shader did before the split was introduced, whose
    comment noted the two forms are equivalent under src-over.
    
    All ten VG expedite tests are within a 2/255 per-channel tolerance;
    ector-suite and evas-suite pass.
    
    Co-Authored-By: Claude Opus 5 (1M context) <[email protected]>
---
 .../engines/gl_common/evas_ector_gl_span_types.h   |  16 +++
 .../evas/engines/gl_common/evas_gl_context.c       | 116 +++++++++------------
 .../engines/gl_generic/evas_ector_gl_span_shader.c |  11 +-
 3 files changed, 74 insertions(+), 69 deletions(-)

diff --git a/src/modules/evas/engines/gl_common/evas_ector_gl_span_types.h b/src/modules/evas/engines/gl_common/evas_ector_gl_span_types.h
index 8a8b517b28..6e858d607d 100644
--- a/src/modules/evas/engines/gl_common/evas_ector_gl_span_types.h
+++ b/src/modules/evas/engines/gl_common/evas_ector_gl_span_types.h
@@ -35,6 +35,22 @@ typedef float GLfloat;
  * --------------------------------------------------------------------------- */
 #define SPAN_FILL_TYPE_GRADIENT_MIN 2
 
+/* ---------------------------------------------------------------------------
+ * Gradient type carried in the .w of a side's grad_def attribute:
+ *   0 linear, 1 radial, 2 solid.
+ *
+ * "Solid" is how a plain colour rides in a gradient variant.  A shape with a
+ * gradient fill and a solid stroke would otherwise need two programs and so
+ * two draw calls; encoding the solid side as a degenerate gradient lets one
+ * draw cover both.  Such a side puts its premultiplied colour in the four
+ * components of grad_abc_y - the slots a linear gradient uses for its
+ * coefficients and ramp row - and the shader takes it verbatim instead of
+ * sampling the ramp atlas.
+ * --------------------------------------------------------------------------- */
+#define SPAN_GRAD_TYPE_LINEAR 0
+#define SPAN_GRAD_TYPE_RADIAL 1
+#define SPAN_GRAD_TYPE_SOLID  2
+
 /* ---------------------------------------------------------------------------
  * Span_Variant — selects the interleaved vertex layout for a given draw call.
  *
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 265165d2a2..866bd057f9 100644
--- a/src/modules/evas/engines/gl_common/evas_gl_context.c
+++ b/src/modules/evas/engines/gl_common/evas_gl_context.c
@@ -2101,6 +2101,39 @@ static Evas_GL_Program _span_prog_dummy;
  *
  * NDC quad order: TL(0), TR(1), BR(2), BL(3) — two triangles: 0,1,2 + 0,2,3.
  */
+/* Write one side's gradient attributes.  A side that is not actually a
+ * gradient travels as SPAN_GRAD_TYPE_SOLID with its colour in abc_y, so that
+ * a mixed fill/stroke shape needs one program rather than two. */
+static void
+_span_side_grad_set(GLfloat abc_y[4], GLfloat def[4], GLfloat radial[4],
+                    const Span_Channel_Params *side)
+{
+   if (side->type < SPAN_FILL_TYPE_GRADIENT_MIN)
+     {
+        abc_y[0] = (float)((side->col >> 16) & 0xFF) / 255.0f;
+        abc_y[1] = (float)((side->col >>  8) & 0xFF) / 255.0f;
+        abc_y[2] = (float)( side->col        & 0xFF) / 255.0f;
+        abc_y[3] = (float)((side->col >> 24) & 0xFF) / 255.0f;
+        def[0] = def[1] = def[2] = 0.0f;
+        def[3] = (GLfloat)SPAN_GRAD_TYPE_SOLID;
+        radial[0] = radial[1] = radial[2] = radial[3] = 0.0f;
+        return;
+     }
+
+   abc_y[0]  = side->grad_a;
+   abc_y[1]  = side->grad_b;
+   abc_y[2]  = side->grad_c;
+   abc_y[3]  = side->grad_ramp_y;
+   def[0]    = side->grad_d;
+   def[1]    = side->grad_e;
+   def[2]    = side->grad_f;
+   def[3]    = (GLfloat)side->grad_type;
+   radial[0] = side->grad_ra;
+   radial[1] = side->grad_rdx;
+   radial[2] = side->grad_rdy;
+   radial[3] = (GLfloat)side->grad_spread;
+}
+
 static void
 _span_fill_vertices(void *out_buf, Span_Variant variant,
                     const Span_Pipe_Params *p,
@@ -2181,30 +2214,10 @@ _span_fill_vertices(void *out_buf, Span_Variant variant,
                 Span_Vertex_Gradient *o =
                    (Span_Vertex_Gradient *)((char *)out_buf + v * sizeof(*o));
                 o->c = common;
-                o->fill_grad_abc_y[0]    = p->fill.grad_a;
-                o->fill_grad_abc_y[1]    = p->fill.grad_b;
-                o->fill_grad_abc_y[2]    = p->fill.grad_c;
-                o->fill_grad_abc_y[3]    = p->fill.grad_ramp_y;
-                o->fill_grad_def[0]      = p->fill.grad_d;
-                o->fill_grad_def[1]      = p->fill.grad_e;
-                o->fill_grad_def[2]      = p->fill.grad_f;
-                o->fill_grad_def[3]      = (GLfloat)p->fill.grad_type;
-                o->fill_grad_radial[0]   = p->fill.grad_ra;
-                o->fill_grad_radial[1]   = p->fill.grad_rdx;
-                o->fill_grad_radial[2]   = p->fill.grad_rdy;
-                o->fill_grad_radial[3]   = (GLfloat)p->fill.grad_spread;
-                o->stroke_grad_abc_y[0]  = p->stroke.grad_a;
-                o->stroke_grad_abc_y[1]  = p->stroke.grad_b;
-                o->stroke_grad_abc_y[2]  = p->stroke.grad_c;
-                o->stroke_grad_abc_y[3]  = p->stroke.grad_ramp_y;
-                o->stroke_grad_def[0]    = p->stroke.grad_d;
-                o->stroke_grad_def[1]    = p->stroke.grad_e;
-                o->stroke_grad_def[2]    = p->stroke.grad_f;
-                o->stroke_grad_def[3]    = (GLfloat)p->stroke.grad_type;
-                o->stroke_grad_radial[0] = p->stroke.grad_ra;
-                o->stroke_grad_radial[1] = p->stroke.grad_rdx;
-                o->stroke_grad_radial[2] = p->stroke.grad_rdy;
-                o->stroke_grad_radial[3] = (GLfloat)p->stroke.grad_spread;
+                _span_side_grad_set(o->fill_grad_abc_y, o->fill_grad_def,
+                                    o->fill_grad_radial, &p->fill);
+                _span_side_grad_set(o->stroke_grad_abc_y, o->stroke_grad_def,
+                                    o->stroke_grad_radial, &p->stroke);
                 break;
              }
            case SPAN_VARIANT_GRADIENT_MASK:
@@ -2212,30 +2225,10 @@ _span_fill_vertices(void *out_buf, Span_Variant variant,
                 Span_Vertex_Gradient_Mask *o =
                    (Span_Vertex_Gradient_Mask *)((char *)out_buf + v * sizeof(*o));
                 o->g.c = common;
-                o->g.fill_grad_abc_y[0]    = p->fill.grad_a;
-                o->g.fill_grad_abc_y[1]    = p->fill.grad_b;
-                o->g.fill_grad_abc_y[2]    = p->fill.grad_c;
-                o->g.fill_grad_abc_y[3]    = p->fill.grad_ramp_y;
-                o->g.fill_grad_def[0]      = p->fill.grad_d;
-                o->g.fill_grad_def[1]      = p->fill.grad_e;
-                o->g.fill_grad_def[2]      = p->fill.grad_f;
-                o->g.fill_grad_def[3]      = (GLfloat)p->fill.grad_type;
-                o->g.fill_grad_radial[0]   = p->fill.grad_ra;
-                o->g.fill_grad_radial[1]   = p->fill.grad_rdx;
-                o->g.fill_grad_radial[2]   = p->fill.grad_rdy;
-                o->g.fill_grad_radial[3]   = (GLfloat)p->fill.grad_spread;
-                o->g.stroke_grad_abc_y[0]  = p->stroke.grad_a;
-                o->g.stroke_grad_abc_y[1]  = p->stroke.grad_b;
-                o->g.stroke_grad_abc_y[2]  = p->stroke.grad_c;
-                o->g.stroke_grad_abc_y[3]  = p->stroke.grad_ramp_y;
-                o->g.stroke_grad_def[0]    = p->stroke.grad_d;
-                o->g.stroke_grad_def[1]    = p->stroke.grad_e;
-                o->g.stroke_grad_def[2]    = p->stroke.grad_f;
-                o->g.stroke_grad_def[3]    = (GLfloat)p->stroke.grad_type;
-                o->g.stroke_grad_radial[0] = p->stroke.grad_ra;
-                o->g.stroke_grad_radial[1] = p->stroke.grad_rdx;
-                o->g.stroke_grad_radial[2] = p->stroke.grad_rdy;
-                o->g.stroke_grad_radial[3] = (GLfloat)p->stroke.grad_spread;
+                _span_side_grad_set(o->g.fill_grad_abc_y, o->g.fill_grad_def,
+                                    o->g.fill_grad_radial, &p->fill);
+                _span_side_grad_set(o->g.stroke_grad_abc_y, o->g.stroke_grad_def,
+                                    o->g.stroke_grad_radial, &p->stroke);
                 o->mask_off_size[0] = p->mask_off_x;
                 o->mask_off_size[1] = p->mask_off_y;
                 o->mask_off_size[2] = p->mask_w;
@@ -2324,27 +2317,14 @@ evas_gl_common_context_span_push(Evas_Engine_GL_Context *gc,
    const int fill_is_solid  = (p->fill.tex   && !fill_is_grad);
    const int stroke_is_solid = (p->stroke.tex && !stroke_is_grad);
 
-   /* Mixed family: one side is gradient and the other is solid.
-    * The gradient and solid main bodies are separate shader programs,
-    * so a mixed shape cannot be drawn in a single pipe entry.  Split
-    * into two recursive pushes — one per side — and let the merge
-    * predicate group each half with the appropriate program.  The
-    * src-over blend is associative, so two passes produce the same
-    * visual result as the old single-pass shader that processed both
-    * sides sequentially within one fragment. */
-   if ((fill_is_grad && stroke_is_solid) || (fill_is_solid && stroke_is_grad))
-     {
-        /* At most one level of recursion: each child call has one tex
-         * zeroed, so it can never re-enter this branch. */
-        Span_Pipe_Params q;
-        q = *p; q.stroke.tex = 0;
-        evas_gl_common_context_span_push(gc, &q, ndc_quad);
-        q = *p; q.fill.tex = 0;
-        evas_gl_common_context_span_push(gc, &q, ndc_quad);
-        return;
-     }
+   /* A mixed shape - gradient one side, plain colour the other - used to be
+    * split into two pushes, because the two had separate programs and so
+    * could not share a pipe entry.  That doubled the draw calls for a very
+    * ordinary shape.  The gradient program now carries a plain colour as
+    * SPAN_GRAD_TYPE_SOLID, so one entry covers both sides. */
+   (void)fill_is_solid;
+   (void)stroke_is_solid;
 
-   /* Single-family case (both grad, both solid, or only one side bound). */
    Span_Variant variant;
    if (fill_is_grad || stroke_is_grad)
      variant = (p->mask_tex != 0) ? SPAN_VARIANT_GRADIENT_MASK
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 5e36772987..8fd5c05612 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
@@ -526,7 +526,15 @@ static const char _glsl_scan_gradient_spans[] =
    "      if (len == 0) break;\n"
    "      sx += gap;\n"
    "      if (int(px) >= sx && int(px) < sx + len) {\n"
+   "         vec4 grad_col;\n"
    "         SPAN_HP float t;\n"
+   "         if (gtype == 2) {\n"
+   "            /* Plain colour riding in a gradient variant: the four\n"
+   "             * gradient-coefficient slots carry it verbatim, so that a\n"
+   "             * shape with a gradient fill and a solid stroke needs one\n"
+   "             * program and one draw rather than two. */\n"
+   "            grad_col = vec4(ga, gb, gc, ramp_v);\n"
+   "         } else {\n"
    "         if (gtype == 1) {\n"
    "            /* Radial gradient: quadratic solve in gradient space */\n"
    "            SPAN_HP float rx = ga * px + gb * py + gc;\n"
@@ -545,7 +553,8 @@ static const char _glsl_scan_gradient_spans[] =
    "            t = ga * px + gb * py + gc;\n"
    "         }\n"
    "         t = grad_spread(t, gspread);\n"
-   "         vec4 grad_col = texture2D(u_grad_ramp_atlas, vec2(t, ramp_v));\n"
+   "         grad_col = texture2D(u_grad_ramp_atlas, vec2(t, ramp_v));\n"
+   "         }\n"
    "         vec4 col = grad_col * cov;\n"
    "         res.rgb = col.rgb + res.rgb * (1.0 - col.a);\n"
    "         res.a   = col.a  + res.a   * (1.0 - col.a);\n"

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

Reply via email to