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.