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 dd06e3449d339c83f54f6ec6b304c8fba45b1ab3
Author: [email protected] <[email protected]>
AuthorDate: Tue May 5 16:21:26 2026 -0600
refactor(evas_ector_gl): introduce per-variant span vertex structs
The span-buffer GL renderer currently routes all per-shape state through
uniform uploads — one glUniform call per shape. To enable quad batching,
per-shape attributes must move into interleaved per-vertex data so that
multiple shapes can coalesce into a single glDrawArrays call. This commit
introduces the four interleaved Span_Vertex_* structs (Solid, Solid_Mask,
Gradient, Gradient_Mask) and a Span_Variant enum that tags which struct
a pipe entry holds.
## Per-Variant Struct Design
Each variant encodes its specific set of per-shape parameters into a
repeatable vertex layout:
- **Span_Vertex_Solid** (24f = 96 B): common header + fill/stroke color
- **Span_Vertex_Solid_Mask** (30f = 120 B): Solid + mask offset/size + comp method
- **Span_Vertex_Gradient** (40f = 160 B): common header + gradient coefficients
(fill abc/ramp_y/def/radial, stroke abc/ramp_y/def/radial)
- **Span_Vertex_Gradient_Mask** (46f = 184 B): Gradient + mask offset/size + comp method
All variants share a 16f (64 B) common header with NDC position, FBO offset,
stroke/fill texture flags, x_min thresholds, and premultiplied color.
The mask_comp_inv[2] field in mask variants encodes:
[0] = raw composite method (cast to float for shader decoding)
[1] = invert flag (1.0 if MATTE_ALPHA_INVERSE, 0.0 for MATTE_ALPHA)
## Pipe Entry State
Evas_Engine_GL_Context.pipe[].array gains four new fields:
- span_vertex_data: heap-grown buffer of interleaved structs
- span_vertex_data_size: bytes allocated
- span_vertex_data_used: bytes filled (reset at flush)
- span_variant: tag indicating which struct variant is in use
High-water mark pattern: size is allocated on first push and reused
across flush cycles; used is reset at flush. Freed during context cleanup.
## Dual-Write Transitional Path
evas_gl_common_context_span_push is refactored into two parts:
1. **_span_fill_vertices helper**: takes NDC quad (4 corners, 8 floats) and
fills 6 vertices (two triangles: TL,TR,BR / TL,BR,BL) of the selected
variant struct. All per-shape metadata is replicated identically across
all 6 vertices; pos varies per-vertex from the NDC quad corners.
2. **_span_pipe_find_or_alloc helper**: extracted merge-predicate logic to
decide whether to reuse the current pipe or allocate a new one. The
predicate still rejects all merges (conservative for now; Task 4
will simplify it to ~6 fields and enable batching).
The new span_push path is a dual-write:
- Canvas-space coords still flow through array.vertex via PUSH_6_VERTICES
(used by the existing flush path, unchanged)
- NDC coords + per-shape data also land in span_vertex_data (filled but
unused at flush-time; Task 4 will flip it to the primary path)
This keeps Task 3 byte-identical on desktop GL: shaders continue reading
from per-shape uniforms, quads remain unbatched, and both test suites
(ector: 19/0/0, evas: 120/0/0) pass unchanged.
## Type Header Extraction
The new evas_ector_gl_span_types.h is a header-only file with:
- Span_Variant enum and four struct definitions
- span_vertex_size(variant) helper
- SPAN_FILL_TYPE_GRADIENT_MIN macro (replaces magic ">= 2" literal)
- SPAN_PIPE_MAX_QUADS constant (1024 quads max per pipe entry)
Zero dependencies on sw_ft_raster.h, GL headers, or EFL private headers —
allows sharing between gl_common (which cannot include gl_generic's
static library headers) and gl_generic. Both now include this single
canonical source of truth.
## NDC Conversion in eng_ector_end
The span_push caller (eng_ector_end) pre-converts the canvas-space quad
to NDC before passing it down. Conversion formula:
ndc_coord = (canvas_coord / canvas_size) * 2.0 - 1.0
Four corners (TL, TR, BR, BL) are computed in order and passed as
ndc_quad[8].
Verification:
- ector suite: 19/0/0
- evas suite: 120/0/0
- Build clean
- Expedite VG benchmark: 10/10 PNGs byte-identical
Co-Authored-By: Claude Opus 4.7 (1M context) <[email protected]>
---
.../engines/gl_common/evas_ector_gl_span_types.h | 100 ++++
.../evas/engines/gl_common/evas_gl_common.h | 16 +-
.../evas/engines/gl_common/evas_gl_context.c | 531 +++++++++++++++------
.../evas/engines/gl_generic/evas_ector_gl_span.h | 8 +
src/modules/evas/engines/gl_generic/evas_engine.c | 17 +-
src/modules/evas/engines/gl_generic/meson.build | 1 +
6 files changed, 517 insertions(+), 156 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
new file mode 100644
index 0000000000..8a8b517b28
--- /dev/null
+++ b/src/modules/evas/engines/gl_common/evas_ector_gl_span_types.h
@@ -0,0 +1,100 @@
+/* SPDX-License-Identifier: LGPL-2.1-only */
+/*
+ * Shared type definitions for the span-buffer GL batching path.
+ *
+ * This header is the single source of truth for:
+ * - Span_Variant enum
+ * - Span_Vertex_* interleaved vertex structs
+ * - span_vertex_size() helper
+ * - SPAN_PIPE_MAX_QUADS and SPAN_FILL_TYPE_GRADIENT_MIN macros
+ *
+ * Deliberately has NO dependency on sw_ft_raster.h, GL headers, or any
+ * EFL private header so it can be included from both gl_common (which
+ * cannot reach gl_generic's static-lib includes) and gl_generic.
+ *
+ * GLfloat is resolved via the GL headers that both callers include before
+ * reaching this header. In test builds that lack GL headers, define
+ * GLfloat as float before including.
+ */
+
+#ifndef EVAS_ECTOR_GL_SPAN_TYPES_H
+#define EVAS_ECTOR_GL_SPAN_TYPES_H
+
+# ifndef GL_FLOAT
+/* Provide GLfloat for standalone/test include contexts that don't include
+ * full GL headers before this header. Real GL builds define GL_FLOAT
+ * (and therefore GLfloat) via their GL headers. */
+typedef float GLfloat;
+# endif
+
+/* ---------------------------------------------------------------------------
+ * Fill-type threshold: Span_Data_Type values >= this are gradient types.
+ * Span_Data_Type: 1=Solid, 2=LinearGradient, 3=RadialGradient.
+ * evas_gl_context.c cannot include evas_ector_gl_span.h (sw_ft_raster.h
+ * dependency), so it tests fill.type against this macro instead of the enum.
+ * --------------------------------------------------------------------------- */
+#define SPAN_FILL_TYPE_GRADIENT_MIN 2
+
+/* ---------------------------------------------------------------------------
+ * Span_Variant — selects the interleaved vertex layout for a given draw call.
+ *
+ * SOLID — no gradient, no mask
+ * SOLID_MASK — no gradient, composite mask present
+ * GRADIENT — gradient fill or stroke, no mask
+ * GRADIENT_MASK — gradient fill or stroke, composite mask present
+ * --------------------------------------------------------------------------- */
+typedef enum
+{
+ SPAN_VARIANT_SOLID = 0,
+ SPAN_VARIANT_SOLID_MASK = 1,
+ SPAN_VARIANT_GRADIENT = 2,
+ SPAN_VARIANT_GRADIENT_MASK = 3,
+ SPAN_VARIANT_COUNT
+} Span_Variant;
+
+/* ---------------------------------------------------------------------------
+ * Per-variant interleaved vertex structs for the span attribute-batching path.
+ *
+ * All sizes are in GLfloat units (4 bytes each). Byte totals are shown in
+ * the trailing comments. Layout must stay in sync with the vertex shader
+ * attribute offsets documented in:
+ * docs/superpowers/specs/2026-05-05-span-gl-attribute-batching-design.md
+ *
+ * mask_comp_inv[2]:
+ * [0] — raw comp_method (Efl_Gfx_Vg_Composite_Method enum value, cast to GLfloat)
+ * [1] — mask_inv flag (1.0 if the mask alpha should be inverted, 0.0 otherwise)
+ * --------------------------------------------------------------------------- */
+
+/** Common fields shared by all four variants. 16f = 64 B */
+typedef struct { GLfloat pos[2]; GLfloat fbo_fill_off[4]; GLfloat stroke_off_flags[4]; GLfloat x_min[2]; GLfloat mul_col[4]; } Span_Vertex_Common;
+
+/** Solid fill, no mask. 24f = 96 B */
+typedef struct { Span_Vertex_Common c; GLfloat fill_col[4]; GLfloat stroke_col[4]; } Span_Vertex_Solid;
+
+/** Solid fill with composite mask. 30f = 120 B */
+typedef struct { Span_Vertex_Solid s; GLfloat mask_off_size[4]; GLfloat mask_comp_inv[2]; } Span_Vertex_Solid_Mask;
+
+/** Gradient fill or stroke, no mask. 40f = 160 B */
+typedef struct { Span_Vertex_Common c; GLfloat fill_grad_abc_y[4]; GLfloat fill_grad_def[4]; GLfloat fill_grad_radial[4]; GLfloat stroke_grad_abc_y[4]; GLfloat stroke_grad_def[4]; GLfloat stroke_grad_radial[4]; } Span_Vertex_Gradient;
+
+/** Gradient fill or stroke with composite mask. 46f = 184 B */
+typedef struct { Span_Vertex_Gradient g; GLfloat mask_off_size[4]; GLfloat mask_comp_inv[2]; } Span_Vertex_Gradient_Mask;
+
+/** Return the per-vertex byte size for the given variant. */
+static inline size_t
+span_vertex_size(Span_Variant v)
+{
+ switch (v)
+ {
+ case SPAN_VARIANT_SOLID: return sizeof(Span_Vertex_Solid);
+ case SPAN_VARIANT_SOLID_MASK: return sizeof(Span_Vertex_Solid_Mask);
+ case SPAN_VARIANT_GRADIENT: return sizeof(Span_Vertex_Gradient);
+ case SPAN_VARIANT_GRADIENT_MASK: return sizeof(Span_Vertex_Gradient_Mask);
+ default: return sizeof(Span_Vertex_Solid);
+ }
+}
+
+/** Maximum number of quads per pipe entry in the span attribute-batching path. */
+#define SPAN_PIPE_MAX_QUADS 1024
+
+#endif /* EVAS_ECTOR_GL_SPAN_TYPES_H */
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 d499819c6d..4e9ce9a25e 100644
--- a/src/modules/evas/engines/gl_common/evas_gl_common.h
+++ b/src/modules/evas/engines/gl_common/evas_gl_common.h
@@ -46,6 +46,11 @@
#include "evas_gl_define.h"
+/* Per-variant span-buffer interleaved vertex types, shared macros, and
+ * the SPAN_FILL_TYPE_GRADIENT_MIN threshold constant.
+ * Single source of truth — no duplication with evas_ector_gl_span.h. */
+#include "evas_ector_gl_span_types.h"
+
#define EVAS_GL_TILE_SIZE 16
#define SHAD_VERTEX 0
@@ -454,6 +459,14 @@ struct _Evas_Engine_GL_Context
Eina_Bool use_mask : 1;
Eina_Bool use_masksam : 1;
Eina_Bool anti_alias : 1;
+ /* Span-buffer attribute batching (Task 3+).
+ * Heap-grown interleaved buffer of Span_Vertex_<variant> structs.
+ * 6 vertices per quad (two triangles). Filled at push-time;
+ * currently unused at flush-time (Task 4 will flip this). */
+ void *span_vertex_data; /* heap-grown; NULL until first push */
+ size_t span_vertex_data_size; /* bytes allocated */
+ size_t span_vertex_data_used; /* bytes filled */
+ Span_Variant span_variant; /* SOLID / SOLID_MASK / GRADIENT / GRADIENT_MASK */
} array;
} pipe[MAX_PIPES];
@@ -703,7 +716,8 @@ void evas_gl_common_context_rectangle_push(Evas_Engine_GL_Context *
Evas_GL_Texture *mtex, int mx, int my, int mw, int mh,
Eina_Bool mask_smooth, Eina_Bool mask_color);
void evas_gl_common_context_span_push(Evas_Engine_GL_Context *gc,
- const Span_Pipe_Params *p);
+ 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 59c6ff39b0..16465f9d71 100644
--- a/src/modules/evas/engines/gl_common/evas_gl_context.c
+++ b/src/modules/evas/engines/gl_common/evas_gl_context.c
@@ -1461,6 +1461,13 @@ evas_gl_common_context_free(Evas_Engine_GL_Context *gc)
PIPE_FREE(gc->pipe[i].array.mask);
PIPE_FREE(gc->pipe[i].array.masksam);
FREE(gc->pipe[i].array.filter_data);
+ if (gc->pipe[i].array.span_vertex_data)
+ {
+ free(gc->pipe[i].array.span_vertex_data);
+ gc->pipe[i].array.span_vertex_data = NULL;
+ gc->pipe[i].array.span_vertex_data_size = 0;
+ gc->pipe[i].array.span_vertex_data_used = 0;
+ }
}
}
@@ -2079,180 +2086,391 @@ evas_gl_common_context_rectangle_push(Evas_Engine_GL_Context *gc,
* from the standard flush path. */
static Evas_GL_Program _span_prog_dummy;
+/* Fill 6 vertices of the per-variant interleaved struct from Span_Pipe_Params
+ * and pre-computed NDC quad corners. pos varies per vertex; all other fields
+ * are replicated identically across the 6 vertices of a quad.
+ *
+ * NDC quad order: TL(0), TR(1), BR(2), BL(3) — two triangles: 0,1,2 + 0,2,3.
+ */
+static void
+_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 */)
+{
+ const int idx[6] = { 0, 1, 2, 0, 2, 3 }; /* triangle fan indices into ndc_quad */
+
+ /* Mask inv: 0=normal, 1=invert, derived from comp_method same as _span_draw_pass */
+ float mask_inv = 0.0f;
+ if (p->comp_method == 2 || p->comp_method == 4) mask_inv = 1.0f;
+
+ /* Build the common header once; pos is overwritten per vertex in the loop. */
+ Span_Vertex_Common common;
+ common.fbo_fill_off[0] = p->fbo_off_x;
+ common.fbo_fill_off[1] = p->fbo_off_y;
+ common.fbo_fill_off[2] = p->fill.off_tx;
+ common.fbo_fill_off[3] = p->fill.off_ty;
+ common.stroke_off_flags[0] = p->stroke.off_tx;
+ common.stroke_off_flags[1] = p->stroke.off_ty;
+ common.stroke_off_flags[2] = (GLfloat)p->max_spans;
+ common.stroke_off_flags[3] = (GLfloat)((p->fill.tex ? 1 : 0) |
+ (p->stroke.tex ? 2 : 0));
+ common.x_min[0] = (GLfloat)p->fill.x_min;
+ common.x_min[1] = (GLfloat)p->stroke.x_min;
+ /* mul_col: premultiplied ARGB 0xAARRGGBB — decode as R,G,B,A for the shader */
+ common.mul_col[0] = (float)((p->mul_col >> 16) & 0xFF) / 255.0f; /* R */
+ common.mul_col[1] = (float)((p->mul_col >> 8) & 0xFF) / 255.0f; /* G */
+ common.mul_col[2] = (float)( p->mul_col & 0xFF) / 255.0f; /* B */
+ common.mul_col[3] = (float)((p->mul_col >> 24) & 0xFF) / 255.0f; /* A */
+
+ for (int v = 0; v < 6; v++)
+ {
+ const int corner = idx[v];
+ common.pos[0] = ndc_quad[corner * 2 + 0];
+ common.pos[1] = ndc_quad[corner * 2 + 1];
+
+ switch (variant)
+ {
+ case SPAN_VARIANT_SOLID:
+ {
+ Span_Vertex_Solid *o =
+ (Span_Vertex_Solid *)((char *)out_buf + v * sizeof(*o));
+ o->c = common;
+ o->fill_col[0] = (float)((p->fill.col >> 16) & 0xFF) / 255.0f;
+ o->fill_col[1] = (float)((p->fill.col >> 8) & 0xFF) / 255.0f;
+ o->fill_col[2] = (float)( p->fill.col & 0xFF) / 255.0f;
+ o->fill_col[3] = (float)((p->fill.col >> 24) & 0xFF) / 255.0f;
+ o->stroke_col[0] = (float)((p->stroke.col >> 16) & 0xFF) / 255.0f;
+ o->stroke_col[1] = (float)((p->stroke.col >> 8) & 0xFF) / 255.0f;
+ o->stroke_col[2] = (float)( p->stroke.col & 0xFF) / 255.0f;
+ o->stroke_col[3] = (float)((p->stroke.col >> 24) & 0xFF) / 255.0f;
+ break;
+ }
+ case SPAN_VARIANT_SOLID_MASK:
+ {
+ Span_Vertex_Solid_Mask *o =
+ (Span_Vertex_Solid_Mask *)((char *)out_buf + v * sizeof(*o));
+ o->s.c = common;
+ o->s.fill_col[0] = (float)((p->fill.col >> 16) & 0xFF) / 255.0f;
+ o->s.fill_col[1] = (float)((p->fill.col >> 8) & 0xFF) / 255.0f;
+ o->s.fill_col[2] = (float)( p->fill.col & 0xFF) / 255.0f;
+ o->s.fill_col[3] = (float)((p->fill.col >> 24) & 0xFF) / 255.0f;
+ o->s.stroke_col[0] = (float)((p->stroke.col >> 16) & 0xFF) / 255.0f;
+ o->s.stroke_col[1] = (float)((p->stroke.col >> 8) & 0xFF) / 255.0f;
+ o->s.stroke_col[2] = (float)( p->stroke.col & 0xFF) / 255.0f;
+ o->s.stroke_col[3] = (float)((p->stroke.col >> 24) & 0xFF) / 255.0f;
+ 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;
+ o->mask_off_size[3] = p->mask_h;
+ o->mask_comp_inv[0] = (GLfloat)p->comp_method;
+ o->mask_comp_inv[1] = mask_inv;
+ break;
+ }
+ case SPAN_VARIANT_GRADIENT:
+ {
+ 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;
+ break;
+ }
+ case SPAN_VARIANT_GRADIENT_MASK:
+ {
+ 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;
+ 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;
+ o->mask_off_size[3] = p->mask_h;
+ o->mask_comp_inv[0] = (GLfloat)p->comp_method;
+ o->mask_comp_inv[1] = mask_inv;
+ break;
+ }
+ default: break;
+ }
+ }
+}
+
+/* Find an existing mergeable pipe entry or allocate a new one.
+ * Returns pipe index, or -1 if flush loop was triggered (caller retries). */
+static int
+_span_pipe_find_or_alloc(Evas_Engine_GL_Context *gc,
+ const Span_Pipe_Params *p,
+ Span_Variant variant,
+ float _inv_tw, float _inv_th)
+{
+ int pn = gc->state.top_pipe;
+
+#define _S gc->pipe[pn].shader
+ if (gc->pipe[pn].array.num > 0)
+ {
+ Eina_Bool can_merge = EINA_FALSE;
+
+ if (gc->pipe[pn].region.type == SHD_SPAN &&
+ _S.span_fill_tex == p->fill.tex &&
+ _S.span_fill_off_tx == p->fill.off_tx &&
+ _S.span_fill_off_ty == p->fill.off_ty &&
+ _S.span_fill_col == p->fill.col &&
+ _S.span_stroke_tex == p->stroke.tex &&
+ _S.span_stroke_off_tx == p->stroke.off_tx &&
+ _S.span_stroke_off_ty == p->stroke.off_ty &&
+ _S.span_stroke_col == p->stroke.col &&
+ _S.span_inv_tw == _inv_tw &&
+ _S.span_inv_th == _inv_th &&
+ _S.span_max_spans == p->max_spans &&
+ _S.span_mul_col == p->mul_col &&
+ _S.span_fill_type == p->fill.type &&
+ _S.span_stroke_type == p->stroke.type &&
+ _S.span_fill_grad_a == p->fill.grad_a &&
+ _S.span_fill_grad_b == p->fill.grad_b &&
+ _S.span_fill_grad_c == p->fill.grad_c &&
+ _S.span_fill_grad_spread == p->fill.grad_spread &&
+ _S.span_fill_grad_ramp_y == p->fill.grad_ramp_y &&
+ _S.span_fill_grad_type == p->fill.grad_type &&
+ _S.span_fill_grad_d == p->fill.grad_d &&
+ _S.span_fill_grad_e == p->fill.grad_e &&
+ _S.span_fill_grad_f == p->fill.grad_f &&
+ _S.span_fill_grad_ra == p->fill.grad_ra &&
+ _S.span_fill_grad_rdx == p->fill.grad_rdx &&
+ _S.span_fill_grad_rdy == p->fill.grad_rdy &&
+ _S.span_stroke_grad_a == p->stroke.grad_a &&
+ _S.span_stroke_grad_b == p->stroke.grad_b &&
+ _S.span_stroke_grad_c == p->stroke.grad_c &&
+ _S.span_stroke_grad_spread == p->stroke.grad_spread &&
+ _S.span_stroke_grad_ramp_y == p->stroke.grad_ramp_y &&
+ _S.span_stroke_grad_type == p->stroke.grad_type &&
+ _S.span_stroke_grad_d == p->stroke.grad_d &&
+ _S.span_stroke_grad_e == p->stroke.grad_e &&
+ _S.span_stroke_grad_f == p->stroke.grad_f &&
+ _S.span_stroke_grad_ra == p->stroke.grad_ra &&
+ _S.span_stroke_grad_rdx == p->stroke.grad_rdx &&
+ _S.span_stroke_grad_rdy == p->stroke.grad_rdy &&
+ _S.span_fbo_off_x == p->fbo_off_x &&
+ _S.span_fbo_off_y == p->fbo_off_y &&
+ _S.span_fill_x_min == p->fill.x_min &&
+ _S.span_stroke_x_min == p->stroke.x_min &&
+ _S.span_grad_atlas_tex == p->grad_atlas_tex &&
+ _S.span_mask_tex == p->mask_tex &&
+ _S.span_comp_method == p->comp_method &&
+ _S.span_mask_w == p->mask_w &&
+ _S.span_mask_h == p->mask_h &&
+ _S.span_mask_off_x == p->mask_off_x &&
+ _S.span_mask_off_y == p->mask_off_y)
+ can_merge = EINA_TRUE;
+
+ /* 1024-quad cap: even a matching entry is full if it's at capacity. */
+ if (can_merge)
+ {
+ size_t vsize = span_vertex_size(variant);
+ if (gc->pipe[pn].array.span_vertex_data_used / vsize
+ >= (size_t)SPAN_PIPE_MAX_QUADS * 6)
+ can_merge = EINA_FALSE; /* full — fall through to new entry */
+ }
+
+ if (!can_merge)
+ {
+ pn = gc->state.top_pipe + 1;
+ if (pn >= gc->shared->info.tune.pipes.max)
+ return -1; /* caller must flush and retry */
+ gc->state.top_pipe = pn;
+ }
+ }
+#undef _S
+ return pn;
+}
+
void
evas_gl_common_context_span_push(Evas_Engine_GL_Context *gc,
- const Span_Pipe_Params *p)
+ const Span_Pipe_Params *p,
+ const GLfloat ndc_quad[8])
{
- int pn = 0;
- /* Precompute reciprocals once; used in both the merge predicate and
- * the write block below. */
+ /* Precompute reciprocals once for the merge predicate. */
float _inv_tw = 1.0f / (float)p->pool_w;
float _inv_th = 1.0f / (float)p->pool_h;
- /* SHD_SPAN pipes carry per-shape uniforms (textures, colors, offsets,
- * gradient params). Two pushes may merge ONLY when every uniform
- * value matches — otherwise the second push's uniforms overwrite the
- * first's. Field-by-field comparison with early exit; this also
- * catches the atlas case where shapes share a GL texture name but
- * differ in offset/color. */
+ /* Determine variant from fill/stroke gradient types and mask presence.
+ * Span_Data_Type values: 1=Solid, 2=LinearGradient, 3=RadialGradient.
+ * SPAN_FILL_TYPE_GRADIENT_MIN == 2 (defined in evas_ector_gl_span_types.h). */
+ Span_Variant variant;
+ if (p->fill.type >= SPAN_FILL_TYPE_GRADIENT_MIN ||
+ p->stroke.type >= SPAN_FILL_TYPE_GRADIENT_MIN)
+ variant = (p->mask_tex != 0) ? SPAN_VARIANT_GRADIENT_MASK
+ : SPAN_VARIANT_GRADIENT;
+ else
+ variant = (p->mask_tex != 0) ? SPAN_VARIANT_SOLID_MASK
+ : SPAN_VARIANT_SOLID;
+
+ int pn;
+ again:
+ pn = _span_pipe_find_or_alloc(gc, p, variant, _inv_tw, _inv_th);
+ if (pn < 0)
+ {
+ shader_array_flush(gc);
+ goto again;
+ }
#define _S gc->pipe[pn].shader
- /* Float == is intentional: both sides come from the same integer-
- * derived computation path, so values are bit-identical when equal. */
- {
- again:
- pn = gc->state.top_pipe;
- if (gc->pipe[pn].array.num > 0)
- {
- Eina_Bool can_merge = EINA_FALSE;
-
- if (gc->pipe[pn].region.type == SHD_SPAN &&
- _S.span_fill_tex == p->fill.tex &&
- _S.span_fill_off_tx == p->fill.off_tx &&
- _S.span_fill_off_ty == p->fill.off_ty &&
- _S.span_fill_col == p->fill.col &&
- _S.span_stroke_tex == p->stroke.tex &&
- _S.span_stroke_off_tx == p->stroke.off_tx &&
- _S.span_stroke_off_ty == p->stroke.off_ty &&
- _S.span_stroke_col == p->stroke.col &&
- _S.span_inv_tw == _inv_tw &&
- _S.span_inv_th == _inv_th &&
- _S.span_max_spans == p->max_spans &&
- _S.span_mul_col == p->mul_col &&
- _S.span_fill_type == p->fill.type &&
- _S.span_stroke_type == p->stroke.type &&
- _S.span_fill_grad_a == p->fill.grad_a &&
- _S.span_fill_grad_b == p->fill.grad_b &&
- _S.span_fill_grad_c == p->fill.grad_c &&
- _S.span_fill_grad_spread == p->fill.grad_spread &&
- _S.span_fill_grad_ramp_y == p->fill.grad_ramp_y &&
- _S.span_fill_grad_type == p->fill.grad_type &&
- _S.span_fill_grad_d == p->fill.grad_d &&
- _S.span_fill_grad_e == p->fill.grad_e &&
- _S.span_fill_grad_f == p->fill.grad_f &&
- _S.span_fill_grad_ra == p->fill.grad_ra &&
- _S.span_fill_grad_rdx == p->fill.grad_rdx &&
- _S.span_fill_grad_rdy == p->fill.grad_rdy &&
- _S.span_stroke_grad_a == p->stroke.grad_a &&
- _S.span_stroke_grad_b == p->stroke.grad_b &&
- _S.span_stroke_grad_c == p->stroke.grad_c &&
- _S.span_stroke_grad_spread == p->stroke.grad_spread &&
- _S.span_stroke_grad_ramp_y == p->stroke.grad_ramp_y &&
- _S.span_stroke_grad_type == p->stroke.grad_type &&
- _S.span_stroke_grad_d == p->stroke.grad_d &&
- _S.span_stroke_grad_e == p->stroke.grad_e &&
- _S.span_stroke_grad_f == p->stroke.grad_f &&
- _S.span_stroke_grad_ra == p->stroke.grad_ra &&
- _S.span_stroke_grad_rdx == p->stroke.grad_rdx &&
- _S.span_stroke_grad_rdy == p->stroke.grad_rdy &&
- _S.span_fbo_off_x == p->fbo_off_x &&
- _S.span_fbo_off_y == p->fbo_off_y &&
- _S.span_fill_x_min == p->fill.x_min &&
- _S.span_stroke_x_min == p->stroke.x_min &&
- _S.span_grad_atlas_tex == p->grad_atlas_tex &&
- _S.span_mask_tex == p->mask_tex &&
- _S.span_comp_method == p->comp_method &&
- _S.span_mask_w == p->mask_w &&
- _S.span_mask_h == p->mask_h &&
- _S.span_mask_off_x == p->mask_off_x &&
- _S.span_mask_off_y == p->mask_off_y)
- can_merge = EINA_TRUE;
-
- if (!can_merge)
- {
- pn = gc->state.top_pipe + 1;
- if (pn >= gc->shared->info.tune.pipes.max)
- {
- shader_array_flush(gc);
- goto again;
- }
- gc->state.top_pipe = pn;
- }
- }
- vertex_array_size_check(gc, pn, 6);
- }
-
/* Write span uniform fields — for a merge this is a redundant
* overwrite with identical values; for a new pipe it initialises. */
- {
- gc->pipe[pn].region.type = SHD_SPAN;
- gc->pipe[pn].shader.prog = &_span_prog_dummy;
- gc->pipe[pn].shader.cur_tex = p->fill.tex ? p->fill.tex : p->stroke.tex;
- gc->pipe[pn].shader.blend = EINA_TRUE;
- gc->pipe[pn].shader.render_op = EVAS_RENDER_BLEND;
- gc->pipe[pn].shader.clip = 0;
- gc->pipe[pn].shader.smooth = 0;
+ gc->pipe[pn].region.type = SHD_SPAN;
+ gc->pipe[pn].shader.prog = &_span_prog_dummy;
+ gc->pipe[pn].shader.cur_tex = p->fill.tex ? p->fill.tex : p->stroke.tex;
+ gc->pipe[pn].shader.blend = EINA_TRUE;
+ gc->pipe[pn].shader.render_op = EVAS_RENDER_BLEND;
+ gc->pipe[pn].shader.clip = 0;
+ gc->pipe[pn].shader.smooth = 0;
- _S.span_fill_tex = p->fill.tex;
- _S.span_fill_off_tx = p->fill.off_tx;
- _S.span_fill_off_ty = p->fill.off_ty;
- _S.span_fill_col = p->fill.col;
- _S.span_stroke_tex = p->stroke.tex;
- _S.span_stroke_off_tx = p->stroke.off_tx;
- _S.span_stroke_off_ty = p->stroke.off_ty;
- _S.span_stroke_col = p->stroke.col;
- _S.span_inv_tw = _inv_tw;
- _S.span_inv_th = _inv_th;
- _S.span_max_spans = p->max_spans;
- _S.span_mul_col = p->mul_col;
- _S.span_fill_type = p->fill.type;
- _S.span_stroke_type = p->stroke.type;
+ _S.span_fill_tex = p->fill.tex;
+ _S.span_fill_off_tx = p->fill.off_tx;
+ _S.span_fill_off_ty = p->fill.off_ty;
+ _S.span_fill_col = p->fill.col;
+ _S.span_stroke_tex = p->stroke.tex;
+ _S.span_stroke_off_tx = p->stroke.off_tx;
+ _S.span_stroke_off_ty = p->stroke.off_ty;
+ _S.span_stroke_col = p->stroke.col;
+ _S.span_inv_tw = _inv_tw;
+ _S.span_inv_th = _inv_th;
+ _S.span_max_spans = p->max_spans;
+ _S.span_mul_col = p->mul_col;
+ _S.span_fill_type = p->fill.type;
+ _S.span_stroke_type = p->stroke.type;
- _S.span_fill_grad_a = p->fill.grad_a;
- _S.span_fill_grad_b = p->fill.grad_b;
- _S.span_fill_grad_c = p->fill.grad_c;
- _S.span_fill_grad_spread = p->fill.grad_spread;
- _S.span_fill_grad_ramp_y = p->fill.grad_ramp_y;
- _S.span_fill_grad_type = p->fill.grad_type;
- _S.span_fill_grad_d = p->fill.grad_d;
- _S.span_fill_grad_e = p->fill.grad_e;
- _S.span_fill_grad_f = p->fill.grad_f;
- _S.span_fill_grad_ra = p->fill.grad_ra;
- _S.span_fill_grad_rdx = p->fill.grad_rdx;
- _S.span_fill_grad_rdy = p->fill.grad_rdy;
+ _S.span_fill_grad_a = p->fill.grad_a;
+ _S.span_fill_grad_b = p->fill.grad_b;
+ _S.span_fill_grad_c = p->fill.grad_c;
+ _S.span_fill_grad_spread = p->fill.grad_spread;
+ _S.span_fill_grad_ramp_y = p->fill.grad_ramp_y;
+ _S.span_fill_grad_type = p->fill.grad_type;
+ _S.span_fill_grad_d = p->fill.grad_d;
+ _S.span_fill_grad_e = p->fill.grad_e;
+ _S.span_fill_grad_f = p->fill.grad_f;
+ _S.span_fill_grad_ra = p->fill.grad_ra;
+ _S.span_fill_grad_rdx = p->fill.grad_rdx;
+ _S.span_fill_grad_rdy = p->fill.grad_rdy;
- _S.span_stroke_grad_a = p->stroke.grad_a;
- _S.span_stroke_grad_b = p->stroke.grad_b;
- _S.span_stroke_grad_c = p->stroke.grad_c;
- _S.span_stroke_grad_spread = p->stroke.grad_spread;
- _S.span_stroke_grad_ramp_y = p->stroke.grad_ramp_y;
- _S.span_stroke_grad_type = p->stroke.grad_type;
- _S.span_stroke_grad_d = p->stroke.grad_d;
- _S.span_stroke_grad_e = p->stroke.grad_e;
- _S.span_stroke_grad_f = p->stroke.grad_f;
- _S.span_stroke_grad_ra = p->stroke.grad_ra;
- _S.span_stroke_grad_rdx = p->stroke.grad_rdx;
- _S.span_stroke_grad_rdy = p->stroke.grad_rdy;
+ _S.span_stroke_grad_a = p->stroke.grad_a;
+ _S.span_stroke_grad_b = p->stroke.grad_b;
+ _S.span_stroke_grad_c = p->stroke.grad_c;
+ _S.span_stroke_grad_spread = p->stroke.grad_spread;
+ _S.span_stroke_grad_ramp_y = p->stroke.grad_ramp_y;
+ _S.span_stroke_grad_type = p->stroke.grad_type;
+ _S.span_stroke_grad_d = p->stroke.grad_d;
+ _S.span_stroke_grad_e = p->stroke.grad_e;
+ _S.span_stroke_grad_f = p->stroke.grad_f;
+ _S.span_stroke_grad_ra = p->stroke.grad_ra;
+ _S.span_stroke_grad_rdx = p->stroke.grad_rdx;
+ _S.span_stroke_grad_rdy = p->stroke.grad_rdy;
- _S.span_fbo_off_x = p->fbo_off_x;
- _S.span_fbo_off_y = p->fbo_off_y;
- _S.span_fill_x_min = p->fill.x_min;
- _S.span_stroke_x_min = p->stroke.x_min;
+ _S.span_fbo_off_x = p->fbo_off_x;
+ _S.span_fbo_off_y = p->fbo_off_y;
+ _S.span_fill_x_min = p->fill.x_min;
+ _S.span_stroke_x_min = p->stroke.x_min;
- _S.span_grad_atlas_tex = p->grad_atlas_tex;
- _S.span_mask_tex = p->mask_tex;
- _S.span_comp_method = p->comp_method;
- _S.span_mask_w = p->mask_w;
- _S.span_mask_h = p->mask_h;
- _S.span_mask_off_x = p->mask_off_x;
- _S.span_mask_off_y = p->mask_off_y;
-
- gc->pipe[pn].array.line = 0;
- gc->pipe[pn].array.use_vertex = 1;
- gc->pipe[pn].array.use_color = 0;
- gc->pipe[pn].array.use_texuv = 0;
- gc->pipe[pn].array.use_texuv2 = 0;
- gc->pipe[pn].array.use_texuv3 = 0;
- gc->pipe[pn].array.use_texa = 0;
- gc->pipe[pn].array.use_texsam = 0;
- gc->pipe[pn].array.use_mask = 0;
- gc->pipe[pn].array.use_masksam = 0;
- }
+ _S.span_grad_atlas_tex = p->grad_atlas_tex;
+ _S.span_mask_tex = p->mask_tex;
+ _S.span_comp_method = p->comp_method;
+ _S.span_mask_w = p->mask_w;
+ _S.span_mask_h = p->mask_h;
+ _S.span_mask_off_x = p->mask_off_x;
+ _S.span_mask_off_y = p->mask_off_y;
#undef _S
+ gc->pipe[pn].array.line = 0;
+ gc->pipe[pn].array.use_vertex = 1;
+ gc->pipe[pn].array.use_color = 0;
+ gc->pipe[pn].array.use_texuv = 0;
+ gc->pipe[pn].array.use_texuv2 = 0;
+ gc->pipe[pn].array.use_texuv3 = 0;
+ gc->pipe[pn].array.use_texa = 0;
+ gc->pipe[pn].array.use_texsam = 0;
+ gc->pipe[pn].array.use_mask = 0;
+ gc->pipe[pn].array.use_masksam = 0;
+
pipe_region_expand(gc, pn, p->x, p->y, p->w, p->h);
+ vertex_array_size_check(gc, pn, 6);
PIPE_GROW(gc, pn, 6);
PUSH_6_VERTICES(pn, p->x, p->y, p->w, p->h);
+
+ /* --- Dual-write: also fill span_vertex_data (NDC, Task 3).
+ * The existing array.vertex upload (canvas-space, above) continues to
+ * drive the existing flush. span_vertex_data carries NDC and is
+ * currently unused at flush-time; Task 4 will flip it to the source
+ * of truth and delete the array.vertex path for span pipes. */
+ {
+ const size_t vsize = span_vertex_size(variant);
+ const size_t needed = gc->pipe[pn].array.span_vertex_data_used + 6 * vsize;
+ if (gc->pipe[pn].array.span_vertex_data_size < needed)
+ {
+ size_t new_size = gc->pipe[pn].array.span_vertex_data_size;
+ if (new_size == 0) new_size = vsize * 6;
+ while (new_size < needed) new_size *= 2;
+ void *grown = realloc(gc->pipe[pn].array.span_vertex_data, new_size);
+ if (grown)
+ {
+ gc->pipe[pn].array.span_vertex_data = grown;
+ gc->pipe[pn].array.span_vertex_data_size = new_size;
+ }
+ /* If realloc fails we silently skip the struct fill for this push.
+ * The existing array.vertex path still works, so rendering is
+ * unaffected. */
+ }
+ if (gc->pipe[pn].array.span_vertex_data_size >= needed)
+ {
+ 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);
+ gc->pipe[pn].array.span_vertex_data_used += 6 * vsize;
+ gc->pipe[pn].array.span_variant = variant;
+ }
+ }
}
#define SWAP(a, b, tmp) \
@@ -4208,6 +4426,10 @@ shader_array_flush(Evas_Engine_GL_Context *gc)
* subsequent IMAGE push expects them. */
gc->pipe[i].array.num = 0;
gc->pipe[i].array.alloc = 0;
+ /* Reset span_vertex_data_used so the buffer is reused
+ * from the start on the next flush cycle (size stays
+ * allocated as a high-water mark). */
+ gc->pipe[i].array.span_vertex_data_used = 0;
continue;
}
@@ -4906,6 +5128,7 @@ shader_array_flush(Evas_Engine_GL_Context *gc)
gc->pipe[i].array.num = 0;
gc->pipe[i].array.alloc = 0;
+ gc->pipe[i].array.span_vertex_data_used = 0;
if (glsym_glMapBuffer && glsym_glUnmapBuffer)
{
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 5c2e0fba2d..858296c103 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
@@ -430,4 +430,12 @@ void span_debug_readback(const char *label, unsigned int tex_id,
#endif /* SPAN_DEBUG_PROBES */
#endif /* EVAS_GL_COMMON_H */
+/* ------------------------------------------------------------------ */
+/* Per-variant interleaved vertex structs (Task 3) */
+/* ------------------------------------------------------------------ */
+
+/* Per-variant vertex types, SPAN_PIPE_MAX_QUADS, SPAN_FILL_TYPE_GRADIENT_MIN.
+ * Single source of truth shared with gl_common (no sw_ft_raster.h dependency). */
+#include "../gl_common/evas_ector_gl_span_types.h"
+
#endif /* EVAS_ECTOR_GL_SPAN_H_ */
diff --git a/src/modules/evas/engines/gl_generic/evas_engine.c b/src/modules/evas/engines/gl_generic/evas_engine.c
index c7352e35ca..a51b03bb81 100644
--- a/src/modules/evas/engines/gl_generic/evas_engine.c
+++ b/src/modules/evas/engines/gl_generic/evas_engine.c
@@ -3312,7 +3312,22 @@ eng_ector_end(void *engine,
}
}
- evas_gl_common_context_span_push(gc, &_spp);
+ /* 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. */
+ GLfloat _ndc[8];
+ float _gw = (float)(gc->w ? gc->w : 1);
+ float _gh = (float)(gc->h ? gc->h : 1);
+ float _x0 = (float)_spp.x;
+ float _y0 = (float)_spp.y;
+ float _x1 = _x0 + (float)_spp.w;
+ float _y1 = _y0 + (float)_spp.h;
+ _ndc[0] = _x0 / _gw * 2.0f - 1.0f; _ndc[1] = _y0 / _gh * 2.0f - 1.0f; /* TL */
+ _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);
}
}
}
diff --git a/src/modules/evas/engines/gl_generic/meson.build b/src/modules/evas/engines/gl_generic/meson.build
index b969ed3494..bc5ca99bbb 100644
--- a/src/modules/evas/engines/gl_generic/meson.build
+++ b/src/modules/evas/engines/gl_generic/meson.build
@@ -21,6 +21,7 @@ engine_src = files([
common_engine_src = [
'evas_gl_private.h',
'evas_gl_common.h',
+ 'evas_ector_gl_span_types.h',
'evas_gl_define.h',
'evas_gl_context.c',
'evas_gl_file_cache.c',
--
To stop receiving notification emails like this one, please contact
the administrator of this repository.