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 942b914f1988327397dc52edb08fb0d76fa8c079
Author: [email protected] <[email protected]>
AuthorDate: Tue Apr 7 10:01:55 2026 -0600
refactor(evas_ector_gl): consolidate span-buffer GLSL and gradient logic
Eliminates ~279 lines of code duplication across the span-buffer rendering
pipeline by extracting common patterns into reusable helpers and shared GLSL
fragments. This improves maintainability and reduces the risk of logic
skew between fill/stroke or solid/gradient/mask shader variants.
1. GLSL shader deduplication (evas_ector_gl_span_shader.c):
- Replaced 4 monolithic shader strings (~520 lines) with 14 shared GLSL
fragments assembled via multi-part glShaderSource arrays
- Introduced _glsl_precision, _glsl_uniforms_common, _glsl_uniforms_solid,
_glsl_uniforms_gradient, _glsl_uniforms_mask, _glsl_scan_spans,
_glsl_grad_spread, _glsl_main_solid_body, _glsl_main_gradient_body,
etc. — each covers one logical concern
- Four shader arrays (_solid_shader_parts, _solid_mask_shader_parts,
_gradient_shader_parts, _gradient_mask_shader_parts) compose these
fragments to produce the final shader programs
- Renamed _compile_shader → _compile_shader_parts to reflect the new
multi-part interface; _link_program now accepts arrays instead of
single strings
- Benefit: Common code (uniforms, scan_spans logic) is defined once;
shader variants reuse fragments instead of duplicating full programs
2. Gradient coefficient helper (evas_engine.c):
- Extracted _compute_gradient_coeffs() to replace duplicated 41-line
fill/stroke gradient coefficient blocks in eng_ector_end
- Handles both linear and radial gradients, uploads ramp textures,
and downgrades degenerate radial to solid with fallback color
- Benefit: Coefficient computation and degenerate checks are now defined
once; fill and stroke paths call the same function
3. Row-tail memset helper (evas_ector_gl_span.c):
- Extracted _flush_row_tail(sc, ti, y) to replace duplicated row-tail
memset logic in _collect_spans_solid (appeared at 2 sites)
- Memsets from span_counts[y] to max_spans+1, writing the len=0
sentinel and clearing stale data in one cache-friendly operation
- Benefit: Single definition avoids skew; maintainers only need to
update one place if the memset pattern changes
4. Composite method predicate (efl_canvas_vg_container.c):
- Introduced _comp_method_needs_mask() to replace two 4-method OR chains
checking if composite method requires a mask texture
- Used at lines 283 and 301 to guard GL composite setup
- Benefit: Predicate logic is defined once; adding/removing mask methods
requires only one code change
5. GL composite helper (evas_vg_private.h):
- Introduced _maybe_set_gl_comp() inline helper to guard and call
ector_software_surface_gl_comp_set with proper NULL checks
- Replaces 3 identical guard+call patterns in efl_canvas_vg_container.c
and efl_canvas_vg_object.c
- Benefit: Common guard pattern is factored out; easier to adjust
NULL-safety logic or composite setup in one place
No functional changes. All shader variants produce identical output.
The refactoring reduces visual regression risk by centralizing common
logic and reducing copy-paste errors.
Commits that introduced these patterns:
- GLSL shaders: introduced as separate monolithic strings during Task 2-4
- Gradient logic: added in Task 3 (per-pixel gradients) with fill/stroke
duplication in eng_ector_end
- Row-tail memset: added in Task 2 span collection, appeared twice by
Task 4 during mask integration work
- Composite checks: introduced during Task 4 mask integration in both
render_pre and the draw path
Co-Authored-By: Claude Opus 4.6 (1M context) <[email protected]>
---
src/lib/evas/canvas/efl_canvas_vg_container.c | 35 +-
src/lib/evas/canvas/efl_canvas_vg_object.c | 5 +-
src/lib/evas/canvas/evas_vg_private.h | 19 +
.../evas/engines/gl_generic/evas_ector_gl_span.c | 70 ++-
.../evas/engines/gl_generic/evas_ector_gl_span.h | 6 +
.../engines/gl_generic/evas_ector_gl_span_shader.c | 628 +++++++++------------
src/modules/evas/engines/gl_generic/evas_engine.c | 158 +++---
7 files changed, 434 insertions(+), 487 deletions(-)
diff --git a/src/lib/evas/canvas/efl_canvas_vg_container.c b/src/lib/evas/canvas/efl_canvas_vg_container.c
index d1450ac901..2a2de947a7 100644
--- a/src/lib/evas/canvas/efl_canvas_vg_container.c
+++ b/src/lib/evas/canvas/efl_canvas_vg_container.c
@@ -5,6 +5,19 @@
#define MY_CLASS EFL_CANVAS_VG_CONTAINER_CLASS
+/**
+ * Return EINA_TRUE if the composite method requires a mask texture
+ * (i.e., it belongs to the matte/mask family handled by the GL span path).
+ */
+static inline Eina_Bool
+_comp_method_needs_mask(Efl_Gfx_Vg_Composite_Method m)
+{
+ return (m == EFL_GFX_VG_COMPOSITE_METHOD_MATTE_ALPHA ||
+ m == EFL_GFX_VG_COMPOSITE_METHOD_MATTE_ALPHA_INVERSE ||
+ m == EFL_GFX_VG_COMPOSITE_METHOD_MASK_INTERSECT ||
+ m == EFL_GFX_VG_COMPOSITE_METHOD_MASK_SUBSTRACT);
+}
+
static void
_invalidate_cb(void *data EINA_UNUSED, const Efl_Event *event)
{
@@ -267,18 +280,11 @@ _efl_canvas_vg_container_render_pre(Evas_Object_Protected_Data *vg_pd,
/* Even when the container's flags are NONE (nothing changed), we must
* still propagate the GL composite mask reference to the ector surface
* every frame — eng_ector_end clears it after use. */
- if (pd->comp_target &&
- (pd->comp.method == EFL_GFX_VG_COMPOSITE_METHOD_MATTE_ALPHA ||
- pd->comp.method == EFL_GFX_VG_COMPOSITE_METHOD_MATTE_ALPHA_INVERSE ||
- pd->comp.method == EFL_GFX_VG_COMPOSITE_METHOD_MASK_INTERSECT ||
- pd->comp.method == EFL_GFX_VG_COMPOSITE_METHOD_MASK_SUBSTRACT))
+ if (pd->comp_target && _comp_method_needs_mask(pd->comp.method))
{
Efl_Canvas_Vg_Container_Data *cpd =
efl_data_scope_get(pd->comp_target, MY_CLASS);
- if (cpd && cpd->comp.gl_surface)
- ector_software_surface_gl_comp_set(surface,
- cpd->comp.gl_surface,
- (int)pd->comp.method);
+ _maybe_set_gl_comp(surface, cpd, pd->comp.method);
}
if (nd->flags == EFL_GFX_CHANGE_FLAG_NONE) return;
@@ -292,11 +298,7 @@ _efl_canvas_vg_container_render_pre(Evas_Object_Protected_Data *vg_pd,
//Container may have composite target.
//FIXME : _prepare_comp() should only work in cases with matte or masking.
// This condition is valid because the masking use same type as matte.
- if (pd->comp_target &&
- (pd->comp.method == EFL_GFX_VG_COMPOSITE_METHOD_MATTE_ALPHA ||
- pd->comp.method == EFL_GFX_VG_COMPOSITE_METHOD_MATTE_ALPHA_INVERSE ||
- pd->comp.method == EFL_GFX_VG_COMPOSITE_METHOD_MASK_INTERSECT ||
- pd->comp.method == EFL_GFX_VG_COMPOSITE_METHOD_MASK_SUBSTRACT))
+ if (pd->comp_target && _comp_method_needs_mask(pd->comp.method))
{
comp_method = pd->comp.method;
comp = _prepare_comp(vg_pd, pd->comp_target,
@@ -310,10 +312,7 @@ _efl_canvas_vg_container_render_pre(Evas_Object_Protected_Data *vg_pd,
{
Efl_Canvas_Vg_Container_Data *cpd =
efl_data_scope_get(pd->comp_target, MY_CLASS);
- if (cpd && cpd->comp.gl_surface)
- ector_software_surface_gl_comp_set(surface,
- cpd->comp.gl_surface,
- (int)pd->comp.method);
+ _maybe_set_gl_comp(surface, cpd, pd->comp.method);
}
}
diff --git a/src/lib/evas/canvas/efl_canvas_vg_object.c b/src/lib/evas/canvas/efl_canvas_vg_object.c
index f4001fdcc8..6e4da011e5 100644
--- a/src/lib/evas/canvas/efl_canvas_vg_object.c
+++ b/src/lib/evas/canvas/efl_canvas_vg_object.c
@@ -438,10 +438,7 @@ _evas_vg_render(Evas_Object_Protected_Data *obj, Efl_Canvas_Vg_Object_Data *pd,
{
Efl_Canvas_Vg_Container_Data *cpd =
efl_data_scope_get(cd->comp_target, EFL_CANVAS_VG_CONTAINER_CLASS);
- if (cpd && cpd->comp.gl_surface)
- ector_software_surface_gl_comp_set(ector,
- cpd->comp.gl_surface,
- (int)cd->comp.method);
+ _maybe_set_gl_comp(ector, cpd, cd->comp.method);
}
int alpha = 255;
diff --git a/src/lib/evas/canvas/evas_vg_private.h b/src/lib/evas/canvas/evas_vg_private.h
index e68f82b155..b2f011d6b3 100644
--- a/src/lib/evas/canvas/evas_vg_private.h
+++ b/src/lib/evas/canvas/evas_vg_private.h
@@ -156,6 +156,25 @@ void * evas_cache_vg_surface_key_get(Efl_Canvas_Vg_Node *ro
ECTOR_API void ector_software_surface_gl_comp_set(Ector_Surface *obj, void *gl_surface, int comp_method);
ECTOR_API void ector_software_surface_gl_comp_get(Ector_Surface *obj, void **gl_surface_out, int *comp_method_out);
+/**
+ * Set the GL composite surface on @p ector if @p target_cd holds a valid
+ * gl_surface reference. No-op when @p target_cd is NULL or has no
+ * gl_surface (software render path, or mask not yet rendered).
+ *
+ * @param ector Shared ector surface for this VG render pass.
+ * @param target_cd Container data of the composite target node.
+ * @param method Composite method to propagate.
+ */
+static inline void
+_maybe_set_gl_comp(Ector_Surface *ector,
+ Efl_Canvas_Vg_Container_Data *target_cd,
+ Efl_Gfx_Vg_Composite_Method method)
+{
+ if (target_cd && target_cd->comp.gl_surface)
+ ector_software_surface_gl_comp_set(ector, target_cd->comp.gl_surface,
+ (int)method);
+}
+
void efl_canvas_vg_node_vg_obj_set(Efl_VG *node, Efl_VG *vg_obj, Efl_Canvas_Vg_Object_Data *vd);
void efl_canvas_vg_node_change(Efl_VG *node);
void efl_canvas_vg_container_vg_obj_update(Efl_VG *obj, Efl_Canvas_Vg_Node_Data *nd);
diff --git a/src/modules/evas/engines/gl_generic/evas_ector_gl_span.c b/src/modules/evas/engines/gl_generic/evas_ector_gl_span.c
index 8ee56ecee4..9be566715a 100644
--- a/src/modules/evas/engines/gl_generic/evas_ector_gl_span.c
+++ b/src/modules/evas/engines/gl_generic/evas_ector_gl_span.c
@@ -272,6 +272,10 @@ span_collector_clear(Span_Collector *sc)
sc->color = 0;
sc->mask_surface = NULL;
sc->comp_method = 0;
+
+ /* Reset row-tail flush state for the new frame. */
+ sc->flush_prev_y = -1;
+ sc->flush_prev_ti = -1;
}
/* ------------------------------------------------------------------ */
@@ -628,6 +632,38 @@ _find_texture_for_x(Span_Collector *sc, int x)
return 0;
}
+/* ------------------------------------------------------------------ */
+/* Row-tail sentinel helper */
+/* ------------------------------------------------------------------ */
+
+/**
+ * Zero the tail of a span texture row from the last written entry to the
+ * end of the stride.
+ *
+ * This writes the zero-length sentinel implicitly (the entry at
+ * span_counts[y] has len=0 after zeroing) AND clears any stale data from
+ * prior frames beyond the current frame's last span.
+ *
+ * The memset covers exactly (max_spans + 1 - idx) entries starting at
+ * index idx — only the unused tail, not the full row. When idx == 0 the
+ * entire row is zeroed; when idx == max_spans nothing is done (row full,
+ * sentinel already provided by the spatial split path).
+ *
+ * @param sc Span collector owning the texture.
+ * @param ti Texture index within sc->textures[].
+ * @param y Row index.
+ */
+static inline void
+_flush_row_tail(Span_Collector *sc, int ti, int y)
+{
+ Span_Texture *tex = &sc->textures[ti];
+ int idx = tex->span_counts[y];
+
+ if (idx < sc->max_spans)
+ memset(tex->buffer + ((size_t)y * sc->stride) + ((size_t)idx * 4),
+ 0, (size_t)(sc->max_spans + 1 - idx) * 4);
+}
+
/* ------------------------------------------------------------------ */
/* Solid span collector callback */
/* ------------------------------------------------------------------ */
@@ -662,8 +698,6 @@ _collect_spans_solid(int count, const SW_FT_Span *spans, void *user_data)
Span_Data *sd = (Span_Data *)user_data;
Span_Collector *sc = (Span_Collector *)sd->span_collector;
int ti, idx, y, sx;
- int prev_y = -1;
- int prev_ti = -1;
Span_Texture *tex;
uint8_t *entry;
@@ -702,17 +736,8 @@ _collect_spans_solid(int count, const SW_FT_Span *spans, void *user_data)
* memset is nearly free.
*
* Only fires when y actually changes — not once per span. */
- if (y != prev_y && prev_y >= 0 && prev_ti >= 0)
- {
- Span_Texture *prev_tex = &sc->textures[prev_ti];
- int prev_idx = prev_tex->span_counts[prev_y];
-
- if (prev_idx < sc->max_spans)
- memset(prev_tex->buffer +
- ((size_t)prev_y * sc->stride) + ((size_t)prev_idx * 4),
- 0,
- (size_t)(sc->max_spans + 1 - prev_idx) * 4);
- }
+ if (y != sc->flush_prev_y && sc->flush_prev_y >= 0 && sc->flush_prev_ti >= 0)
+ _flush_row_tail(sc, sc->flush_prev_ti, sc->flush_prev_y);
ti = (sc->texture_count == 1) ? 0 : _find_texture_for_x(sc, sx);
tex = &sc->textures[ti];
@@ -801,28 +826,19 @@ _collect_spans_solid(int count, const SW_FT_Span *spans, void *user_data)
if (idx > sc->actual_max_spans)
sc->actual_max_spans = idx;
- prev_y = y;
- prev_ti = ti;
+ sc->flush_prev_y = y;
+ sc->flush_prev_ti = ti;
spans++;
count--;
}
- /* Memset the tail of the last row after the loop ends.
+ /* Flush the tail of the last row after the loop ends.
* The row-change path above fires only when y changes, so the final
* row (or the only row when the shape spans a single scanline) is
* handled here. */
- if (prev_y >= 0 && prev_ti >= 0)
- {
- Span_Texture *prev_tex = &sc->textures[prev_ti];
- int prev_idx = prev_tex->span_counts[prev_y];
-
- if (prev_idx < sc->max_spans)
- memset(prev_tex->buffer +
- ((size_t)prev_y * sc->stride) + ((size_t)prev_idx * 4),
- 0,
- (size_t)(sc->max_spans + 1 - prev_idx) * 4);
- }
+ if (sc->flush_prev_y >= 0 && sc->flush_prev_ti >= 0)
+ _flush_row_tail(sc, sc->flush_prev_ti, sc->flush_prev_y);
}
/* ------------------------------------------------------------------ */
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 e9d12ea34d..5c2e0fba2d 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
@@ -199,6 +199,12 @@ struct _Span_Collector
* The +1 reserves a dedicated sentinel slot. */
int actual_max_spans; /* max span_counts[y] seen during collection this frame */
+ /* Row-tail flush state — tracked across multiple _collect_spans_solid
+ * invocations (e.g., when _span_fill_clipRect calls the callback in
+ * chunks). Reset in span_collector_clear. */
+ int flush_prev_y; /* last row flushed (-1 = none) */
+ int flush_prev_ti; /* texture index of last flushed row */
+
/* Fill parameters captured at span_collector_new() time or during collection */
Span_Data_Type type; /* Solid, LinearGradient, or RadialGradient */
uint32_t color; /* premultiplied ARGB (0xAARRGGBB) for Solid fills */
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 95d1e28ad8..7417ecffb1 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
@@ -37,201 +37,17 @@ static const char _span_vertex_glsl[] =
" gl_Position = a_position;\n"
"}\n";
-/* --- Solid fragment shader ---
- *
- * Each span entry is 1 texel (4 bytes) in the span texture:
- * byte0 (B): coverage — AA coverage 0-255
- * byte1 (G): len — span length (max 255; longer spans are split)
- * byte2 (R): gap — distance from end of previous span on this row
- * byte3 (A): reserved — zero
- *
- * The shader iterates over up to u_max_spans span entries on the current
- * scanline (identified by gl_FragCoord.y), checks whether the current pixel
- * falls inside each span's x range, and accumulates a coverage-weighted
- * premultiplied-alpha result. A zero-length sentinel terminates the search.
- *
- * u_inv_tw / u_inv_th are 1/pool_w and 1/pool_h (pool-space reciprocals).
- * u_offset.x / u_offset.y are the texel offsets of the span sub-region
- * within the pool. The shader adds these to convert from logical
- * span-buffer coordinates to pool UV coordinates.
- * #define MAX_SPANS must match SPAN_COLLECTOR_DEFAULT_MAX_SPANS (64).
- */
-static const char _span_solid_fragment_glsl[] =
- "precision highp float;\n"
- "uniform sampler2D u_fill_spans;\n"
- "uniform sampler2D u_stroke_spans;\n"
- "uniform float u_inv_tw;\n"
- "uniform float u_inv_th;\n"
- "uniform int u_max_spans;\n"
- "uniform vec4 u_mul_col;\n"
- "uniform vec2 u_fill_offset;\n"
- "uniform vec2 u_stroke_offset;\n"
- "uniform vec2 u_fbo_offset;\n"
- "uniform vec4 u_fill_col;\n"
- "uniform vec4 u_stroke_col;\n"
- "uniform int u_has_fill;\n"
- "uniform int u_has_stroke;\n"
- "uniform int u_fill_x_min;\n"
- "uniform int u_stroke_x_min;\n"
- "#define MAX_SPANS 64\n"
- "\n"
- "/* Scan one span texture row, accumulating coverage-weighted base_col\n"
- " * via premultiplied-alpha src-over into result. */\n"
- "vec4 scan_spans(sampler2D tex, vec2 off, vec4 base_col, float px,\n"
- " float fy, float inv_tw, int max_s, int x_min, vec4 res) {\n"
- " int sx = x_min;\n"
- " for (int i = 0; i < MAX_SPANS; i++) {\n"
- " if (i >= max_s) break;\n"
- " float fx = (off.x + float(i) + 0.5) * inv_tw;\n"
- " vec4 t = texture2D(tex, vec2(fx, fy));\n"
- " int gap = int(t.r * 255.0 + 0.5);\n"
- " int len = int(t.g * 255.0 + 0.5);\n"
- " float cov = t.b;\n"
- " if (len == 0) break;\n"
- " sx += gap;\n"
- " if (int(px) >= sx && int(px) < sx + len) {\n"
- " vec4 col = base_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"
- " }\n"
- " sx += len;\n"
- " }\n"
- " return res;\n"
- "}\n"
- "\n"
- "void main() {\n"
- " float px = gl_FragCoord.x - u_fbo_offset.x;\n"
- " float py = gl_FragCoord.y - u_fbo_offset.y;\n"
- " vec4 result = vec4(0.0);\n"
- "\n"
- " if (u_has_fill == 1) {\n"
- " float fy = (u_fill_offset.y + py) * u_inv_th;\n"
- " result = scan_spans(u_fill_spans, u_fill_offset, u_fill_col,\n"
- " px, fy, u_inv_tw, u_max_spans, u_fill_x_min, result);\n"
- " }\n"
- " if (u_has_stroke == 1) {\n"
- " float fy = (u_stroke_offset.y + py) * u_inv_th;\n"
- " result = scan_spans(u_stroke_spans, u_stroke_offset, u_stroke_col,\n"
- " px, fy, u_inv_tw, u_max_spans, u_stroke_x_min, result);\n"
- " }\n"
- "\n"
- " gl_FragColor = result * u_mul_col;\n"
- "}\n";
+/* ------------------------------------------------------------------ */
+/* Shared GLSL fragment shader source fragments */
+/* ------------------------------------------------------------------ */
-/* --- Solid mask fragment shader ---
- *
- * Identical to the solid shader but with unconditional composite mask sampling.
- * Used when span_mask_tex != 0. No u_has_mask / u_comp_method uniforms —
- * the mask alpha is always multiplied into the result.
- */
-static const char _span_solid_mask_fragment_glsl[] =
- "precision highp float;\n"
- "uniform sampler2D u_fill_spans;\n"
- "uniform sampler2D u_stroke_spans;\n"
- "uniform float u_inv_tw;\n"
- "uniform float u_inv_th;\n"
- "uniform int u_max_spans;\n"
- "uniform vec4 u_mul_col;\n"
- "uniform vec2 u_fill_offset;\n"
- "uniform vec2 u_stroke_offset;\n"
- "uniform vec2 u_fbo_offset;\n"
- "uniform vec4 u_fill_col;\n"
- "uniform vec4 u_stroke_col;\n"
- "uniform int u_has_fill;\n"
- "uniform int u_has_stroke;\n"
- "uniform int u_fill_x_min;\n"
- "uniform int u_stroke_x_min;\n"
- "uniform sampler2D u_mask_tex;\n"
- "uniform vec2 u_mask_size;\n"
- "uniform vec2 u_mask_offset;\n"
- "uniform float u_mask_inv;\n"
- "uniform float u_mask_op;\n"
- "#define MAX_SPANS 64\n"
- "\n"
- "vec4 scan_spans(sampler2D tex, vec2 off, vec4 base_col, float px,\n"
- " float fy, float inv_tw, int max_s, int x_min, vec4 res) {\n"
- " int sx = x_min;\n"
- " for (int i = 0; i < MAX_SPANS; i++) {\n"
- " if (i >= max_s) break;\n"
- " float fx = (off.x + float(i) + 0.5) * inv_tw;\n"
- " vec4 t = texture2D(tex, vec2(fx, fy));\n"
- " int gap = int(t.r * 255.0 + 0.5);\n"
- " int len = int(t.g * 255.0 + 0.5);\n"
- " float cov = t.b;\n"
- " if (len == 0) break;\n"
- " sx += gap;\n"
- " if (int(px) >= sx && int(px) < sx + len) {\n"
- " vec4 col = base_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"
- " }\n"
- " sx += len;\n"
- " }\n"
- " return res;\n"
- "}\n"
- "\n"
- "void main() {\n"
- " float px = gl_FragCoord.x - u_fbo_offset.x;\n"
- " float py = gl_FragCoord.y - u_fbo_offset.y;\n"
- " vec4 result = vec4(0.0);\n"
- "\n"
- " if (u_has_fill == 1) {\n"
- " float fy = (u_fill_offset.y + py) * u_inv_th;\n"
- " result = scan_spans(u_fill_spans, u_fill_offset, u_fill_col,\n"
- " px, fy, u_inv_tw, u_max_spans, u_fill_x_min, result);\n"
- " }\n"
- " if (u_has_stroke == 1) {\n"
- " float fy = (u_stroke_offset.y + py) * u_inv_th;\n"
- " result = scan_spans(u_stroke_spans, u_stroke_offset, u_stroke_col,\n"
- " px, fy, u_inv_tw, u_max_spans, u_stroke_x_min, result);\n"
- " }\n"
- "\n"
- " /* Force unconditional sampler references (driver workaround). */\n"
- " result += (texture2D(u_fill_spans, vec2(0.0)) +\n"
- " texture2D(u_stroke_spans, vec2(0.0))) * 0.0;\n"
- "\n"
- " vec2 mask_uv = vec2((px + u_mask_offset.x + 0.5) / u_mask_size.x,\n"
- " (py + u_mask_offset.y + 0.5) / u_mask_size.y);\n"
- " float mask_a = texture2D(u_mask_tex, mask_uv).a;\n"
- " /* u_mask_op: 0=multiply, 1=add, 2=difference\n"
- " * u_mask_inv: 0=normal, 1=invert (for multiply path) */\n"
- " if (u_mask_op < 0.5)\n"
- " result *= mix(mask_a, 1.0 - mask_a, u_mask_inv);\n"
- " else if (u_mask_op < 1.5)\n"
- " result = vec4(result.rgb, min(result.a + mask_a, 1.0));\n"
- " else\n"
- " result *= abs(result.a - mask_a);\n"
- "\n"
- " gl_FragColor = result * u_mul_col;\n"
- "}\n";
+/* Shared by all four shaders. */
+static const char _glsl_precision[] =
+ "precision highp float;\n";
-/* --- Gradient fragment shader ---
- *
- * Per-pixel gradient evaluation using a 1024×1 RGBA8 ramp texture.
- *
- * Span buffer format: identical to the solid shader — 1 texel per span
- * (gap, len, coverage). The shader uses the same scan_spans() helper as
- * the solid shader to find which span covers the current pixel. On hit it
- * computes the gradient parameter t per-pixel instead of using a fixed color:
- *
- * t = u_grad_a * gl_FragCoord.x + u_grad_b * gl_FragCoord.y + u_grad_c
- *
- * The three coefficients encode the full inverse-transform + gradient
- * direction in a single dot-product, pre-computed on the CPU in eng_ector_end.
- *
- * Spread modes:
- * u_grad_spread == 0 (PAD): t = clamp(t, 0.0, 1.0)
- * u_grad_spread == 1 (REFLECT): t = 1.0 - abs(fract(t*0.5)*2.0 - 1.0)
- * u_grad_spread == 2 (REPEAT): t = fract(t)
- *
- * The ramp texture is on unit 2 (units 0 and 1 are fill/stroke span textures).
- *
- * For fill and stroke each channel carries independent gradient parameters
- * (u_fill_grad_* vs u_stroke_grad_*) so mixed gradient+gradient shapes
- * render correctly with different gradients per channel.
- */
-static const char _span_gradient_fragment_glsl[] =
- "precision highp float;\n"
+/* 15 base uniforms + MAX_SPANS macro shared by all four shaders.
+ * #define MAX_SPANS must match SPAN_COLLECTOR_DEFAULT_MAX_SPANS (64). */
+static const char _glsl_uniforms_common[] =
"uniform sampler2D u_fill_spans;\n"
"uniform sampler2D u_stroke_spans;\n"
"uniform float u_inv_tw;\n"
@@ -245,6 +61,15 @@ static const char _span_gradient_fragment_glsl[] =
"uniform int u_has_stroke;\n"
"uniform int u_fill_x_min;\n"
"uniform int u_stroke_x_min;\n"
+ "#define MAX_SPANS 64\n";
+
+/* Solid-only color uniforms (absent in gradient shaders → loc returns -1). */
+static const char _glsl_uniforms_solid[] =
+ "uniform vec4 u_fill_col;\n"
+ "uniform vec4 u_stroke_col;\n";
+
+/* 18 gradient coefficient uniforms (fill + stroke). */
+static const char _glsl_uniforms_gradient[] =
"uniform sampler2D u_fill_grad_ramp;\n"
"uniform float u_fill_grad_a;\n"
"uniform float u_fill_grad_b;\n"
@@ -268,10 +93,60 @@ static const char _span_gradient_fragment_glsl[] =
"uniform float u_stroke_grad_f;\n"
"uniform float u_stroke_grad_ra;\n"
"uniform float u_stroke_grad_rdx;\n"
- "uniform float u_stroke_grad_rdy;\n"
- "#define MAX_SPANS 64\n"
+ "uniform float u_stroke_grad_rdy;\n";
+
+/* Composite mask uniforms (present only in *_mask variants). */
+static const char _glsl_uniforms_mask[] =
+ "uniform sampler2D u_mask_tex;\n"
+ "uniform vec2 u_mask_size;\n"
+ "uniform vec2 u_mask_offset;\n"
+ "uniform float u_mask_inv;\n"
+ "uniform float u_mask_op;\n";
+
+/* scan_spans() — shared by solid and solid_mask shaders.
+ *
+ * Each span entry is 1 texel (4 bytes) in the span texture:
+ * byte0 (B): coverage — AA coverage 0-255
+ * byte1 (G): len — span length (max 255; longer spans are split)
+ * byte2 (R): gap — distance from end of previous span on this row
+ * byte3 (A): reserved — zero
+ */
+static const char _glsl_scan_spans[] =
"\n"
- "/* Apply gradient spread mode to t in [−∞, +∞] → [0, 1]. */\n"
+ "/* Scan one span texture row, accumulating coverage-weighted base_col\n"
+ " * via premultiplied-alpha src-over into result. */\n"
+ "vec4 scan_spans(sampler2D tex, vec2 off, vec4 base_col, float px,\n"
+ " float fy, float inv_tw, int max_s, int x_min, vec4 res) {\n"
+ " int sx = x_min;\n"
+ " for (int i = 0; i < MAX_SPANS; i++) {\n"
+ " if (i >= max_s) break;\n"
+ " float fx = (off.x + float(i) + 0.5) * inv_tw;\n"
+ " vec4 t = texture2D(tex, vec2(fx, fy));\n"
+ " int gap = int(t.r * 255.0 + 0.5);\n"
+ " int len = int(t.g * 255.0 + 0.5);\n"
+ " float cov = t.b;\n"
+ " if (len == 0) break;\n"
+ " sx += gap;\n"
+ " if (int(px) >= sx && int(px) < sx + len) {\n"
+ " vec4 col = base_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"
+ " }\n"
+ " sx += len;\n"
+ " }\n"
+ " return res;\n"
+ "}\n";
+
+/* grad_spread() — shared by gradient and gradient_mask shaders.
+ *
+ * Spread modes:
+ * spread == 0 (PAD): t = clamp(t, 0.0, 1.0)
+ * spread == 1 (REFLECT): t = 1.0 - abs(fract(t*0.5)*2.0 - 1.0)
+ * spread == 2 (REPEAT): t = fract(t)
+ */
+static const char _glsl_grad_spread[] =
+ "\n"
+ "/* Apply gradient spread mode to t in [-inf, +inf] -> [0, 1]. */\n"
"float grad_spread(float t, int spread) {\n"
" if (spread == 1) {\n"
" /* REFLECT: mirror at 0 and 1 */\n"
@@ -284,7 +159,15 @@ static const char _span_gradient_fragment_glsl[] =
" t = clamp(t, 0.0, 1.0);\n"
" }\n"
" return t;\n"
- "}\n"
+ "}\n";
+
+/* scan_gradient_spans() — shared by gradient and gradient_mask shaders.
+ *
+ * Span buffer format identical to the solid shader (gap, len, coverage).
+ * On hit, computes gradient parameter t per-pixel (linear or radial) and
+ * samples the gradient ramp texture.
+ */
+static const char _glsl_scan_gradient_spans[] =
"\n"
"/* Scan one gradient span texture row. On hit, compute t per-pixel\n"
" * (linear or radial) and sample the gradient ramp, then src-over\n"
@@ -332,7 +215,30 @@ static const char _span_gradient_fragment_glsl[] =
" sx += len;\n"
" }\n"
" return res;\n"
- "}\n"
+ "}\n";
+
+/* main() body for solid shaders: px/py setup, fill/stroke dispatch.
+ * Everything up to but not including gl_FragColor. */
+static const char _glsl_main_solid_body[] =
+ "\n"
+ "void main() {\n"
+ " float px = gl_FragCoord.x - u_fbo_offset.x;\n"
+ " float py = gl_FragCoord.y - u_fbo_offset.y;\n"
+ " vec4 result = vec4(0.0);\n"
+ "\n"
+ " if (u_has_fill == 1) {\n"
+ " float fy = (u_fill_offset.y + py) * u_inv_th;\n"
+ " result = scan_spans(u_fill_spans, u_fill_offset, u_fill_col,\n"
+ " px, fy, u_inv_tw, u_max_spans, u_fill_x_min, result);\n"
+ " }\n"
+ " if (u_has_stroke == 1) {\n"
+ " float fy = (u_stroke_offset.y + py) * u_inv_th;\n"
+ " result = scan_spans(u_stroke_spans, u_stroke_offset, u_stroke_col,\n"
+ " px, fy, u_inv_tw, u_max_spans, u_stroke_x_min, result);\n"
+ " }\n";
+
+/* main() body for gradient shaders. */
+static const char _glsl_main_gradient_body[] =
"\n"
"void main() {\n"
" float px = gl_FragCoord.x - u_fbo_offset.x;\n"
@@ -360,141 +266,25 @@ static const char _span_gradient_fragment_glsl[] =
" u_stroke_grad_d, u_stroke_grad_e, u_stroke_grad_f,\n"
" u_stroke_grad_ra, u_stroke_grad_rdx, u_stroke_grad_rdy,\n"
" px, py, fy, u_inv_tw, u_max_spans, u_stroke_x_min, result);\n"
- " }\n"
- "\n"
+ " }\n";
+
+/* Common main() ending: multiply by color and close. */
+static const char _glsl_main_end[] =
" gl_FragColor = result * u_mul_col;\n"
"}\n";
-/* --- Gradient mask fragment shader ---
- *
- * Identical to the gradient shader but with unconditional composite mask sampling.
- * Used when span_mask_tex != 0. No u_has_mask / u_comp_method uniforms —
- * the mask alpha is always multiplied into the result.
- */
-static const char _span_gradient_mask_fragment_glsl[] =
- "precision highp float;\n"
- "uniform sampler2D u_fill_spans;\n"
- "uniform sampler2D u_stroke_spans;\n"
- "uniform float u_inv_tw;\n"
- "uniform float u_inv_th;\n"
- "uniform int u_max_spans;\n"
- "uniform vec4 u_mul_col;\n"
- "uniform vec2 u_fill_offset;\n"
- "uniform vec2 u_stroke_offset;\n"
- "uniform vec2 u_fbo_offset;\n"
- "uniform int u_has_fill;\n"
- "uniform int u_has_stroke;\n"
- "uniform int u_fill_x_min;\n"
- "uniform int u_stroke_x_min;\n"
- "uniform sampler2D u_mask_tex;\n"
- "uniform vec2 u_mask_size;\n"
- "uniform vec2 u_mask_offset;\n"
- "uniform float u_mask_inv;\n"
- "uniform float u_mask_op;\n"
- "uniform sampler2D u_fill_grad_ramp;\n"
- "uniform float u_fill_grad_a;\n"
- "uniform float u_fill_grad_b;\n"
- "uniform float u_fill_grad_c;\n"
- "uniform int u_fill_grad_spread;\n"
- "uniform sampler2D u_stroke_grad_ramp;\n"
- "uniform float u_stroke_grad_a;\n"
- "uniform float u_stroke_grad_b;\n"
- "uniform float u_stroke_grad_c;\n"
- "uniform int u_stroke_grad_spread;\n"
- "uniform int u_fill_grad_type;\n"
- "uniform float u_fill_grad_d;\n"
- "uniform float u_fill_grad_e;\n"
- "uniform float u_fill_grad_f;\n"
- "uniform float u_fill_grad_ra;\n"
- "uniform float u_fill_grad_rdx;\n"
- "uniform float u_fill_grad_rdy;\n"
- "uniform int u_stroke_grad_type;\n"
- "uniform float u_stroke_grad_d;\n"
- "uniform float u_stroke_grad_e;\n"
- "uniform float u_stroke_grad_f;\n"
- "uniform float u_stroke_grad_ra;\n"
- "uniform float u_stroke_grad_rdx;\n"
- "uniform float u_stroke_grad_rdy;\n"
- "#define MAX_SPANS 64\n"
+/* Unconditional sampler-keep lines for solid mask shaders.
+ * Forces the driver to preserve fill/stroke span samplers that only
+ * appear inside conditional branches. */
+static const char _glsl_mask_keep_solid[] =
"\n"
- "float grad_spread(float t, int spread) {\n"
- " if (spread == 1) {\n"
- " t = 1.0 - abs(fract(t * 0.5) * 2.0 - 1.0);\n"
- " } else if (spread == 2) {\n"
- " t = fract(t);\n"
- " } else {\n"
- " t = clamp(t, 0.0, 1.0);\n"
- " }\n"
- " return t;\n"
- "}\n"
- "\n"
- "vec4 scan_gradient_spans(sampler2D span_tex, vec2 off,\n"
- " sampler2D ramp, float ga, float gb, float gc,\n"
- " int gspread, int gtype,\n"
- " float gd, float ge, float gf,\n"
- " float gra, float grdx, float grdy,\n"
- " float px, float py,\n"
- " float fy, float inv_tw, int max_s, int x_min, vec4 res) {\n"
- " int sx = x_min;\n"
- " for (int i = 0; i < MAX_SPANS; i++) {\n"
- " if (i >= max_s) break;\n"
- " float fx = (off.x + float(i) + 0.5) * inv_tw;\n"
- " vec4 s = texture2D(span_tex, vec2(fx, fy));\n"
- " int gap = int(s.r * 255.0 + 0.5);\n"
- " int len = int(s.g * 255.0 + 0.5);\n"
- " float cov = s.b;\n"
- " if (len == 0) break;\n"
- " sx += gap;\n"
- " if (int(px) >= sx && int(px) < sx + len) {\n"
- " float t;\n"
- " if (gtype == 1) {\n"
- " float rx = ga * px + gb * py + gc;\n"
- " float ry = gd * px + ge * py + gf;\n"
- " float b_val = 2.0 * (rx * grdx + ry * grdy);\n"
- " float b_s = b_val * gra;\n"
- " float det = b_s * b_s + (rx * rx + ry * ry) * 2.0 * gra;\n"
- " t = sqrt(max(det, 0.0)) - b_s;\n"
- " } else {\n"
- " t = ga * px + gb * py + gc;\n"
- " }\n"
- " t = grad_spread(t, gspread);\n"
- " vec4 grad_col = texture2D(ramp, vec2(t, 0.5));\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"
- " }\n"
- " sx += len;\n"
- " }\n"
- " return res;\n"
- "}\n"
- "\n"
- "void main() {\n"
- " float px = gl_FragCoord.x - u_fbo_offset.x;\n"
- " float py = gl_FragCoord.y - u_fbo_offset.y;\n"
- " vec4 result = vec4(0.0);\n"
- "\n"
- " if (u_has_fill == 1) {\n"
- " float fy = (u_fill_offset.y + py) * u_inv_th;\n"
- " result = scan_gradient_spans(\n"
- " u_fill_spans, u_fill_offset,\n"
- " u_fill_grad_ramp,\n"
- " u_fill_grad_a, u_fill_grad_b, u_fill_grad_c,\n"
- " u_fill_grad_spread, u_fill_grad_type,\n"
- " u_fill_grad_d, u_fill_grad_e, u_fill_grad_f,\n"
- " u_fill_grad_ra, u_fill_grad_rdx, u_fill_grad_rdy,\n"
- " px, py, fy, u_inv_tw, u_max_spans, u_fill_x_min, result);\n"
- " }\n"
- " if (u_has_stroke == 1) {\n"
- " float fy = (u_stroke_offset.y + py) * u_inv_th;\n"
- " result = scan_gradient_spans(\n"
- " u_stroke_spans, u_stroke_offset,\n"
- " u_stroke_grad_ramp,\n"
- " u_stroke_grad_a, u_stroke_grad_b, u_stroke_grad_c,\n"
- " u_stroke_grad_spread, u_stroke_grad_type,\n"
- " u_stroke_grad_d, u_stroke_grad_e, u_stroke_grad_f,\n"
- " u_stroke_grad_ra, u_stroke_grad_rdx, u_stroke_grad_rdy,\n"
- " px, py, fy, u_inv_tw, u_max_spans, u_stroke_x_min, result);\n"
- " }\n"
+ " /* Force unconditional sampler references (driver workaround). */\n"
+ " result += (texture2D(u_fill_spans, vec2(0.0)) +\n"
+ " texture2D(u_stroke_spans, vec2(0.0))) * 0.0;\n";
+
+/* Unconditional sampler-keep lines for gradient mask shaders.
+ * Keeps all four samplers: fill/stroke span and fill/stroke ramp. */
+static const char _glsl_mask_keep_gradient[] =
"\n"
" /* Force all sampler references unconditionally to prevent the GLSL\n"
" * compiler from stripping samplers that only appear inside if-branches.\n"
@@ -503,7 +293,12 @@ static const char _span_gradient_mask_fragment_glsl[] =
" vec4 _keep_stroke = texture2D(u_stroke_spans, vec2(0.0));\n"
" vec4 _keep_framp = texture2D(u_fill_grad_ramp, vec2(0.0));\n"
" vec4 _keep_sramp = texture2D(u_stroke_grad_ramp, vec2(0.0));\n"
- " result += (_keep_fill + _keep_stroke + _keep_framp + _keep_sramp) * 0.0;\n"
+ " result += (_keep_fill + _keep_stroke + _keep_framp + _keep_sramp) * 0.0;\n";
+
+/* Mask epilogue: sample the composite mask texture and apply it.
+ * u_mask_op: 0=multiply, 1=add, 2=difference
+ * u_mask_inv: 0=normal, 1=invert (multiply path only) */
+static const char _glsl_mask_epilogue[] =
"\n"
" vec2 mask_uv = vec2((px + u_mask_offset.x + 0.5) / u_mask_size.x,\n"
" (py + u_mask_offset.y + 0.5) / u_mask_size.y);\n"
@@ -515,9 +310,103 @@ static const char _span_gradient_mask_fragment_glsl[] =
" else if (u_mask_op < 1.5)\n"
" result = vec4(result.rgb, min(result.a + mask_a, 1.0));\n"
" else\n"
- " result *= abs(result.a - mask_a);\n"
- " gl_FragColor = result * u_mul_col;\n"
- "}\n";
+ " result *= abs(result.a - mask_a);\n";
+
+/* ------------------------------------------------------------------ */
+/* Per-shader fragment arrays */
+/* ------------------------------------------------------------------ */
+
+/* --- Solid fragment shader ---
+ *
+ * Each span entry is 1 texel (4 bytes) in the span texture:
+ * byte0 (B): coverage — AA coverage 0-255
+ * byte1 (G): len — span length (max 255; longer spans are split)
+ * byte2 (R): gap — distance from end of previous span on this row
+ * byte3 (A): reserved — zero
+ *
+ * The shader iterates over up to u_max_spans span entries on the current
+ * scanline, checks whether the current pixel falls inside each span's x
+ * range, and accumulates a coverage-weighted premultiplied-alpha result.
+ * A zero-length sentinel terminates the search.
+ *
+ * u_inv_tw / u_inv_th are 1/pool_w and 1/pool_h (pool-space reciprocals).
+ * u_fill_offset / u_stroke_offset are texel offsets within the pool.
+ * #define MAX_SPANS must match SPAN_COLLECTOR_DEFAULT_MAX_SPANS (64).
+ */
+static const char * const _solid_shader_parts[] = {
+ _glsl_precision,
+ _glsl_uniforms_common,
+ _glsl_uniforms_solid,
+ _glsl_scan_spans,
+ _glsl_main_solid_body,
+ _glsl_main_end
+};
+#define _SOLID_SHADER_PARTS \
+ ((int)(sizeof(_solid_shader_parts) / sizeof(_solid_shader_parts[0])))
+
+/* --- Solid mask fragment shader ---
+ *
+ * Identical to the solid shader but with unconditional composite mask
+ * sampling. Used when span_mask_tex != 0. No u_has_mask / u_comp_method
+ * uniforms — the mask alpha is always applied inside the fragment shader.
+ */
+static const char * const _solid_mask_shader_parts[] = {
+ _glsl_precision,
+ _glsl_uniforms_common,
+ _glsl_uniforms_solid,
+ _glsl_uniforms_mask,
+ _glsl_scan_spans,
+ _glsl_main_solid_body,
+ _glsl_mask_keep_solid,
+ _glsl_mask_epilogue,
+ _glsl_main_end
+};
+#define _SOLID_MASK_SHADER_PARTS \
+ ((int)(sizeof(_solid_mask_shader_parts) / sizeof(_solid_mask_shader_parts[0])))
+
+/* --- Gradient fragment shader ---
+ *
+ * Per-pixel gradient evaluation using a 1024×1 RGBA8 ramp texture.
+ *
+ * Span buffer format: identical to the solid shader — 1 texel per span
+ * (gap, len, coverage). On hit it computes the gradient parameter t
+ * per-pixel instead of using a fixed color:
+ * t = u_grad_a * gl_FragCoord.x + u_grad_b * gl_FragCoord.y + u_grad_c
+ *
+ * The ramp texture is on unit 2 (units 0 and 1 are fill/stroke span
+ * textures). Fill and stroke carry independent gradient parameters.
+ */
+static const char * const _gradient_shader_parts[] = {
+ _glsl_precision,
+ _glsl_uniforms_common,
+ _glsl_uniforms_gradient,
+ _glsl_grad_spread,
+ _glsl_scan_gradient_spans,
+ _glsl_main_gradient_body,
+ _glsl_main_end
+};
+#define _GRADIENT_SHADER_PARTS \
+ ((int)(sizeof(_gradient_shader_parts) / sizeof(_gradient_shader_parts[0])))
+
+/* --- Gradient mask fragment shader ---
+ *
+ * Identical to the gradient shader but with unconditional composite mask
+ * sampling. Used when span_mask_tex != 0.
+ */
+static const char * const _gradient_mask_shader_parts[] = {
+ _glsl_precision,
+ _glsl_uniforms_common,
+ _glsl_uniforms_gradient,
+ _glsl_uniforms_mask,
+ _glsl_grad_spread,
+ _glsl_scan_gradient_spans,
+ _glsl_main_gradient_body,
+ _glsl_mask_keep_gradient,
+ _glsl_mask_epilogue,
+ _glsl_main_end
+};
+#define _GRADIENT_MASK_SHADER_PARTS \
+ ((int)(sizeof(_gradient_mask_shader_parts) / sizeof(_gradient_mask_shader_parts[0])))
/* ------------------------------------------------------------------ */
/* Internal shader state */
@@ -590,14 +479,15 @@ static GLuint _white_mask_tex = 0;
/* ------------------------------------------------------------------ */
/**
- * Compile a single shader stage.
+ * Compile a single shader stage from an array of source fragments.
*
- * @param type GL_VERTEX_SHADER or GL_FRAGMENT_SHADER.
- * @param src Null-terminated GLSL source string.
- * @return GL shader object name, or 0 on failure.
+ * @param type GL_VERTEX_SHADER or GL_FRAGMENT_SHADER.
+ * @param parts Array of null-terminated GLSL source strings.
+ * @param count Number of strings in @p parts.
+ * @return GL shader object name, or 0 on failure.
*/
static unsigned int
-_compile_shader(unsigned int type, const char *src)
+_compile_shader_parts(unsigned int type, const char **parts, int count)
{
unsigned int shd;
int ok = 0;
@@ -605,7 +495,7 @@ _compile_shader(unsigned int type, const char *src)
shd = glCreateShader(type);
if (!shd) return 0;
- glShaderSource(shd, 1, &src, NULL);
+ glShaderSource(shd, count, parts, NULL);
glCompileShader(shd);
glGetShaderiv(shd, GL_COMPILE_STATUS, &ok);
if (!ok)
@@ -620,25 +510,29 @@ _compile_shader(unsigned int type, const char *src)
}
/**
- * Compile and link a span shader program.
+ * Compile and link a span shader program from source fragment arrays.
*
* Idempotent: returns EINA_TRUE immediately if the program is already
* compiled (ss->program != 0).
*
- * @param ss Shader state to populate.
- * @param frag_src Fragment shader GLSL source.
- * @return EINA_TRUE on success, EINA_FALSE on compile/link error.
+ * @param ss Shader state to populate.
+ * @param frag_parts Array of fragment shader GLSL source strings.
+ * @param frag_count Number of strings in @p frag_parts.
+ * @return EINA_TRUE on success, EINA_FALSE on compile/link error.
*/
static Eina_Bool
-_link_program(Span_Shader *ss, const char *frag_src)
+_link_program(Span_Shader *ss, const char **frag_parts, int frag_count)
{
unsigned int vs, fs;
int ok = 0;
if (ss->program) return EINA_TRUE; /* already compiled */
- vs = _compile_shader(GL_VERTEX_SHADER, _span_vertex_glsl);
- fs = _compile_shader(GL_FRAGMENT_SHADER, frag_src);
+ {
+ const char *vert_parts[1] = { _span_vertex_glsl };
+ vs = _compile_shader_parts(GL_VERTEX_SHADER, vert_parts, 1);
+ }
+ fs = _compile_shader_parts(GL_FRAGMENT_SHADER, frag_parts, frag_count);
if (!vs || !fs)
{
if (vs) glDeleteShader(vs);
@@ -765,22 +659,26 @@ span_debug_readback(const char *label, GLuint tex_id, int px_x, int px_y)
Eina_Bool
span_shader_init(void)
{
- if (!_link_program(&_solid_shader, _span_solid_fragment_glsl))
+ if (!_link_program(&_solid_shader,
+ (const char **)_solid_shader_parts, _SOLID_SHADER_PARTS))
{
ERR("span solid shader link failed");
return EINA_FALSE;
}
- if (!_link_program(&_gradient_shader, _span_gradient_fragment_glsl))
+ if (!_link_program(&_gradient_shader,
+ (const char **)_gradient_shader_parts, _GRADIENT_SHADER_PARTS))
{
ERR("span gradient shader link failed");
return EINA_FALSE;
}
- if (!_link_program(&_solid_mask_shader, _span_solid_mask_fragment_glsl))
+ if (!_link_program(&_solid_mask_shader,
+ (const char **)_solid_mask_shader_parts, _SOLID_MASK_SHADER_PARTS))
{
ERR("span solid mask shader link failed");
return EINA_FALSE;
}
- if (!_link_program(&_gradient_mask_shader, _span_gradient_mask_fragment_glsl))
+ if (!_link_program(&_gradient_mask_shader,
+ (const char **)_gradient_mask_shader_parts, _GRADIENT_MASK_SHADER_PARTS))
{
ERR("span gradient mask shader link failed");
return EINA_FALSE;
@@ -883,16 +781,30 @@ span_collector_upload_textures(Span_Collector *sc, void *gc_ptr)
}
}
- /* Sentinel write is no longer needed here.
+ /* Write sentinels for rows that have spans.
*
- * _collect_spans_solid memsets the full tail of each row it touches
- * (from span_counts[y] to max_spans+1) during collection, which
- * implicitly writes the len=0 sentinel AND clears stale data from
- * previous frames in a single L1-hot memset.
+ * The collection phase (_flush_row_tail) memsets the tail of each
+ * row it touches, but edge cases in chunked callbacks can leave
+ * rows without a clean sentinel. This per-row single-byte write
+ * is the correctness backstop: it ensures byte[1] (len) at the
+ * span_counts[y] position is 0 for every active row.
*
- * For rows that receive NO spans this frame, span_collector_clear
- * zeroes byte[1] (len) of entry 0 on every row, so the shader
- * sees len=0 at the very first entry and terminates immediately. */
+ * For rows with NO spans, span_collector_clear already zeroed
+ * byte[1] of entry 0, so only rows with idx > 0 need attention. */
+ {
+ int y;
+ for (y = 0; y < sc->height; y++)
+ {
+ int idx = tex->span_counts[y];
+ if (idx > 0 && idx < sc->max_spans)
+ {
+ uint8_t *sentinel = tex->buffer +
+ ((size_t)y * sc->stride) +
+ ((size_t)idx * 4);
+ sentinel[1] = 0;
+ }
+ }
+ }
/* First frame: create the Evas texture via the standard path.
* Subsequent dirty frames: update in-place via glTexSubImage2D.
diff --git a/src/modules/evas/engines/gl_generic/evas_engine.c b/src/modules/evas/engines/gl_generic/evas_engine.c
index 097db361e2..dd09d12a65 100644
--- a/src/modules/evas/engines/gl_generic/evas_engine.c
+++ b/src/modules/evas/engines/gl_generic/evas_engine.c
@@ -3057,6 +3057,71 @@ _span_gradient_radial_coeffs(Ector_Renderer_Software_Gradient_Data *gd,
*out_rdy = (float)gd->radial.dy;
}
+/**
+ * Compute per-channel gradient coefficients for the span shader.
+ *
+ * Inspects sc->type and sc->gradient_data. When sc is non-NULL and holds
+ * gradient data this function fills all out parameters and may downgrade
+ * *inout_shader_type from LinearGradient/RadialGradient to Solid when the
+ * radial geometry degenerates (fradius != 0 or a ≈ 0). When sc is NULL
+ * or has no gradient data all out values are left at their zero defaults.
+ *
+ * @param sc Span collector for this channel (fill or stroke).
+ * @param inout_shader_type On entry: LinearGradient or RadialGradient.
+ * On exit: may be downgraded to Solid.
+ * @param inout_col Solid color — updated when downgraded to Solid.
+ * @param out_ga..out_grdy Output gradient coefficients.
+ * @param out_gs Gradient spread mode (EFL enum → int).
+ * @param out_gramp Uploaded ramp texture GL name.
+ * @param out_gtype 0=linear, 1=radial.
+ */
+static void
+_compute_gradient_coeffs(Span_Collector *sc,
+ int *inout_shader_type, uint32_t *inout_col,
+ float *out_ga, float *out_gb, float *out_gc,
+ int *out_gs, GLuint *out_gramp, int *out_gtype,
+ float *out_gd, float *out_ge, float *out_gf,
+ float *out_gra, float *out_grdx, float *out_grdy)
+{
+ Ector_Renderer_Software_Gradient_Data *gd;
+ int shader_type = *inout_shader_type;
+
+ if (!sc || !sc->gradient_data) return;
+ if (shader_type != (int)LinearGradient && shader_type != (int)RadialGradient) return;
+
+ gd = (Ector_Renderer_Software_Gradient_Data *)sc->gradient_data;
+ *out_gramp = _span_gradient_upload_ramp(sc);
+
+ if (shader_type == (int)LinearGradient)
+ {
+ _span_gradient_linear_coeffs(gd, &sc->inv,
+ sc->grad_offx, sc->grad_offy,
+ 0.0f, 0.0f,
+ out_ga, out_gb, out_gc);
+ *out_gtype = 0;
+ }
+ else /* RadialGradient */
+ {
+ if (gd->radial.fradius >= 0.00001f || fabsf(gd->radial.a) <= 0.00001f)
+ {
+ /* Degenerate radial — fall back to solid using first stop color. */
+ *inout_shader_type = (int)Solid;
+ if (gd->color_table)
+ *inout_col = gd->color_table[0];
+ return;
+ }
+ _span_gradient_radial_coeffs(gd, &sc->inv,
+ sc->grad_offx, sc->grad_offy,
+ 0.0f, 0.0f,
+ out_ga, out_gb, out_gc,
+ out_gd, out_ge, out_gf,
+ out_gra, out_grdx, out_grdy);
+ *out_gtype = 1;
+ }
+
+ *out_gs = (int)gd->gd->s;
+}
+
static void
eng_ector_end(void *engine,
void *surface,
@@ -3192,87 +3257,20 @@ eng_ector_end(void *engine,
float stroke_gd = 0.0f, stroke_ge = 0.0f, stroke_gf = 0.0f;
float stroke_gra = 0.0f, stroke_grdx = 0.0f, stroke_grdy = 0.0f;
- if ((fill_shader_type == (int)LinearGradient ||
- fill_shader_type == (int)RadialGradient) &&
- sc_fill && sc_fill->gradient_data && _rsd)
+ if (_rsd)
{
- Ector_Renderer_Software_Gradient_Data *gd =
- (Ector_Renderer_Software_Gradient_Data *)sc_fill->gradient_data;
-
- fill_gramp = _span_gradient_upload_ramp(sc_fill);
-
- if (fill_shader_type == (int)LinearGradient)
- {
- _span_gradient_linear_coeffs(gd, &sc_fill->inv,
- sc_fill->grad_offx, sc_fill->grad_offy,
- 0.0f, 0.0f,
- &fill_ga, &fill_gb, &fill_gc_coef);
- fill_gtype = 0;
- }
- else if (fill_shader_type == (int)RadialGradient)
- {
- if (gd->radial.fradius >= 0.00001f ||
- fabsf(gd->radial.a) <= 0.00001f)
- {
- fill_shader_type = (int)Solid;
- if (gd->color_table)
- fill_col = gd->color_table[0];
- }
- else
- {
- _span_gradient_radial_coeffs(gd, &sc_fill->inv,
- sc_fill->grad_offx, sc_fill->grad_offy,
- 0.0f, 0.0f,
- &fill_ga, &fill_gb, &fill_gc_coef,
- &fill_gd, &fill_ge, &fill_gf,
- &fill_gra, &fill_grdx, &fill_grdy);
- fill_gtype = 1;
- }
- }
-
- /* Map EFL spread enum to shader int (PAD=0, REFLECT=1, REPEAT=2). */
- fill_gs = (int)gd->gd->s;
- }
-
- if ((stroke_shader_type == (int)LinearGradient ||
- stroke_shader_type == (int)RadialGradient) &&
- sc_stroke && sc_stroke->gradient_data && _rsd)
- {
- Ector_Renderer_Software_Gradient_Data *gd =
- (Ector_Renderer_Software_Gradient_Data *)sc_stroke->gradient_data;
-
- stroke_gramp = _span_gradient_upload_ramp(sc_stroke);
-
- if (stroke_shader_type == (int)LinearGradient)
- {
- _span_gradient_linear_coeffs(gd, &sc_stroke->inv,
- sc_stroke->grad_offx, sc_stroke->grad_offy,
- 0.0f, 0.0f,
- &stroke_ga, &stroke_gb, &stroke_gc_coef);
- stroke_gtype = 0;
- }
- else if (stroke_shader_type == (int)RadialGradient)
- {
- if (gd->radial.fradius >= 0.00001f ||
- fabsf(gd->radial.a) <= 0.00001f)
- {
- stroke_shader_type = (int)Solid;
- if (gd->color_table)
- stroke_col = gd->color_table[0];
- }
- else
- {
- _span_gradient_radial_coeffs(gd, &sc_stroke->inv,
- sc_stroke->grad_offx, sc_stroke->grad_offy,
- 0.0f, 0.0f,
- &stroke_ga, &stroke_gb, &stroke_gc_coef,
- &stroke_gd, &stroke_ge, &stroke_gf,
- &stroke_gra, &stroke_grdx, &stroke_grdy);
- stroke_gtype = 1;
- }
- }
-
- stroke_gs = (int)gd->gd->s;
+ _compute_gradient_coeffs(sc_fill,
+ &fill_shader_type, &fill_col,
+ &fill_ga, &fill_gb, &fill_gc_coef,
+ &fill_gs, &fill_gramp, &fill_gtype,
+ &fill_gd, &fill_ge, &fill_gf,
+ &fill_gra, &fill_grdx, &fill_grdy);
+ _compute_gradient_coeffs(sc_stroke,
+ &stroke_shader_type, &stroke_col,
+ &stroke_ga, &stroke_gb, &stroke_gc_coef,
+ &stroke_gs, &stroke_gramp, &stroke_gtype,
+ &stroke_gd, &stroke_ge, &stroke_gf,
+ &stroke_gra, &stroke_grdx, &stroke_grdy);
}
/* Draw each spatial-split texture within this shape.
--
To stop receiving notification emails like this one, please contact
the administrator of this repository.