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 c3a5bede1e376d1a3c13e67f5eaaf34c5733e179
Author: [email protected] <[email protected]>
AuthorDate: Tue May 5 22:24:42 2026 -0600

    fix(evas_ector_gl): NDC for FBO targets, mixed fill/stroke split
    
    The 5-commit attribute-batching refactor shipped working in the Expedite test set but
    broken in real applications. These two fixes restore correct rendering on both paths.
    
    ## Fix 1: NDC Dimensions for FBO Targets (evas_engine.c)
    
    The NDC conversion site computed divisors by dividing canvas-space coordinates by gc->w/gc->h.
    Those fields hold the main-window dimensions; _evas_gl_common_viewport_set never updates them
    when rendering to an offscreen FBO. When VG content is drawn into an atlas-pool sub-rect
    (the typical path via evas_gl_common_context_target_surface_set), the divisor was wrong by
    an order of magnitude. NDC values clustered near -1 and rendered geometry collapsed into the
    corner where the object should have been. As a secondary symptom, interpolated varyings then
    sampled extreme positions from the span pool, producing wrong colors.
    
    Fix: mirror what shader_array_flush does for non-span pipes—read the target surface from
    gc->pipe[0].shader.surface and use its w/h for FBO targets, fall back to gc->w/h only when
    the target is the default surface (or NULL). Same predicate as shader_array_flush's gate
    (lines 4302-4308).
    
    Verification: Expedite VG benchmark falls back to gc->w/h for default-surface rendering
    (unaffected by this fix); same-program-sampler quads continue to merge correctly.
    
    ## Fix 2: Mixed Fill/Stroke Family Split (evas_gl_context.c)
    
    The 5-commit refactor collapsed the merge predicate but inadvertently dropped the original
    branch that split a shape into two draws when fill and stroke used different shader families
    (solid vs. gradient). Without the split, a shape with gradient fill + solid stroke (or vice versa)
    went into a single pipe entry under the gradient variant. The gradient main body then ran
    scan_gradient_spans unconditionally for both has_f and has_s branches—the solid stroke side,
    lacking valid gradient coefficients, sampled the ramp atlas with garbage values producing
    wrong stroke colors.
    
    Fix: add back the split at the push entry point. Classify each bound side (tex != 0) as solid
    or gradient. If mixed (one grad + one solid), recursively split into two pushes—one with stroke.tex=0
    (fill-only, fill's variant), one with fill.tex=0 (stroke-only, stroke's variant). Each half goes
    to its correct program. Premultiplied src-over is associative, so two-pass output matches the
    original single-pass shader's sequential within-fragment blend. Recursion is bounded to one level
    (each child has one tex zeroed and cannot re-enter the mixed branch).
    
    The variant detection in the single-family fall-through case is also tightened: consult
    fill_is_grad and stroke_is_grad, both of which require tex != 0. An unbound side's type field
    can no longer drive variant selection—closing a parallel correctness gap.
    
    Verification: Expedite VG benchmarks don't exercise mixed fill/stroke families; both fixes
    restore byte-identical output for Expedite's same-family cases. The gap in coverage is
    real-app-scenarios (gradient fill + solid stroke, common in UI icons). User confirmed
    interactive rendering correctness on both fixes.
    
    Why neither fix needed a test added: Expedite VG already exercises gradient + mask paths with
    bound-side-only and same-family cases; both fixes restore byte-identical output there. The
    coverage gap is real-app-scenarios; expanding Expedite is a separate effort.
    
    ector suite: 19/0/0 (unchanged)
    evas suite: 120/0/0 (unchanged)
    Expedite VG `-o` capture: 10/10 PNGs byte-identical
    
    Co-Authored-By: Claude Opus 4.7 (1M context) <[email protected]>
---
 .../evas/engines/gl_common/evas_gl_context.c       | 32 ++++++++++++++++++++--
 src/modules/evas/engines/gl_generic/evas_engine.c  | 27 ++++++++++++++----
 2 files changed, 51 insertions(+), 8 deletions(-)

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 03d6be0f16..bf27e17ce0 100644
--- a/src/modules/evas/engines/gl_common/evas_gl_context.c
+++ b/src/modules/evas/engines/gl_common/evas_gl_context.c
@@ -2298,12 +2298,38 @@ evas_gl_common_context_span_push(Evas_Engine_GL_Context *gc,
    float _inv_tw = 1.0f / (float)p->pool_w;
    float _inv_th = 1.0f / (float)p->pool_h;
 
-   /* Determine variant from fill/stroke gradient types and mask presence.
+   /* Classify each bound side as solid or gradient.  An unbound side
+    * (tex == 0) is "none" and does not influence variant selection.
     * Span_Data_Type values: 1=Solid, 2=LinearGradient, 3=RadialGradient.
     * SPAN_FILL_TYPE_GRADIENT_MIN == 2 (defined in evas_ector_gl_span_types.h). */
+   const int fill_is_grad   = (p->fill.tex   && p->fill.type   >= SPAN_FILL_TYPE_GRADIENT_MIN);
+   const int stroke_is_grad = (p->stroke.tex && p->stroke.type >= SPAN_FILL_TYPE_GRADIENT_MIN);
+   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;
+     }
+
+   /* Single-family case (both grad, both solid, or only one side bound). */
    Span_Variant variant;
-   if (p->fill.type  >= SPAN_FILL_TYPE_GRADIENT_MIN ||
-       p->stroke.type >= SPAN_FILL_TYPE_GRADIENT_MIN)
+   if (fill_is_grad || stroke_is_grad)
      variant = (p->mask_tex != 0) ? SPAN_VARIANT_GRADIENT_MASK
                                    : SPAN_VARIANT_GRADIENT;
    else
diff --git a/src/modules/evas/engines/gl_generic/evas_engine.c b/src/modules/evas/engines/gl_generic/evas_engine.c
index a51b03bb81..2e393b6acd 100644
--- a/src/modules/evas/engines/gl_generic/evas_engine.c
+++ b/src/modules/evas/engines/gl_generic/evas_engine.c
@@ -3313,12 +3313,29 @@ eng_ector_end(void *engine,
                                   }
 
                                 /* Pre-convert canvas-space quad to NDC for
-                                 * span_vertex_data (Task 3 dual-write).
-                                 * array.vertex still receives canvas-space
-                                 * coords via PUSH_6_VERTICES. */
+                                 * span_vertex_data.
+                                 *
+                                 * NDC must be divided by the TARGET SURFACE
+                                 * dimensions, not gc->w/gc->h.  When VG content
+                                 * is drawn into an FBO (the common case — VG
+                                 * renders to an atlas-pool sub-rect via glim),
+                                 * gc->w/h still hold the main window dimensions
+                                 * because _evas_gl_common_viewport_set never
+                                 * updates them for FBO targets.  Using the
+                                 * 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. */
                                 GLfloat _ndc[8];
-                                float _gw = (float)(gc->w ? gc->w : 1);
-                                float _gh = (float)(gc->h ? gc->h : 1);
+                                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 _x0 = (float)_spp.x;
                                 float _y0 = (float)_spp.y;
                                 float _x1 = _x0 + (float)_spp.w;

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

Reply via email to