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 c3512cabf357bb0cb2f36cdd99bbaa63ed602b7b
Author: [email protected] <[email protected]>
AuthorDate: Tue May 5 15:50:50 2026 -0600
feat(evas_ector_gl): route gradient ramps through shared atlas
The span-buffer GL renderer currently uses per-shape uniform textures
for gradient ramps, which prevents quad coalescing even when the merge
predicate is later simplified (Task 3–4). This commit wires the gradient
ramp atlas (introduced in Task 1) into the existing per-shape uniform
rendering path: each gradient shape's ramp lookup returns a row index,
which is stored as a normalized V-coordinate uniform and passed to the
fragment shader. The atlas texture itself is bound once per render pass
instead of per shape, eliminating a per-shape sampler-binding boundary
that would otherwise defeat batching.
Changes:
1. **Gradient ramp atlas lifecycle** — allocated in eng_engine_new and
freed in eng_engine_free, alongside other GL context resources. If
allocation fails, the `disabled` flag is set and gradient shapes are
skipped on push (one-shot ERR log).
2. **Per-render-pass LRU counter** — span_grad_atlas_frame_begin() is
called once at the start of the gradient render block to bump the LRU
frame counter, enabling eviction of least-recently-used rows when the
atlas exceeds 64 distinct ramps in a single frame.
3. **Ramp lookup at push-site** — when a shape has a gradient fill/stroke,
_compute_gradient_coeffs calls span_grad_atlas_lookup with the gradient
identity (Efl_Vg_Gradient*), a content-derived version (FNV-1a hash of
the resolved 4 KB ramp bytes), and the byte buffer. On success, the row
index is converted to a normalized V coordinate via span_grad_atlas_row_to_v
and stored in span_fill_grad_ramp_y / span_stroke_grad_ramp_y. On failure
(atlas disabled), the shape is skipped. This makes the identity fast-path
content-keyed: animated gradient stops force an automatic re-lookup, fixing
a stale-cache bug where pointer-identity caching missed stop changes.
4. **Shader changes** — per-shape sampler uniforms (u_fill_grad_ramp,
u_stroke_grad_ramp) are replaced with per-shape float uniforms
(u_fill_grad_ramp_y, u_stroke_grad_ramp_y) for the atlas V coordinate.
The shared atlas sampler (u_grad_ramp_atlas) is added to the common
uniforms and bound once per render pass on GL_TEXTURE2. Fragment shader
texture2D calls change from texture2D(u_*_grad_ramp, vec2(t, 0.5)) to
texture2D(u_grad_ramp_atlas, vec2(t, ramp_v)), where ramp_v is the
per-shape interpolated uniform.
5. **Span_Pipe_Params migration** — two GLuint ramp texture references
become two float ramp_y coordinates, and a new GLuint grad_atlas_tex
field is added to track the shared atlas texture binding.
Implementation correctly handles:
- Null atlas (gradient shapes are skipped per the spec's error table)
- Animated gradients (version-based caching auto-detects stop changes)
- Empty atlas rows (free slots reused before LRU eviction kicks in)
- Hash collisions (byte-compare fallback in the lookup path)
One draw per shape is preserved (no batching yet — that arrives in
Task 4 when shaders move to varyings and the merge predicate collapses
to 6 fields). The atlas consolidates per-shape textures into one shared
binding, removing a sampler-binding boundary that would otherwise prevent
same-program-same-sampler quads from merging after the attribute refactor.
Verification (all pass):
- ector suite: 19/0/0 (unchanged)
- evas suite: 120/0/0 (unchanged)
- Expedite VG benchmark: 10/10 PNGs byte-identical vs baseline
Note: Two non-blocking cleanups are deferred to Task 5 (dead-infrastructure
removal): struct fields grad_ramp_tex and grad_ramp_crc in Span_Pipe_Params,
and an unreachable glDeleteTextures block in the old ramp path. Both are
marked with comments for easy spot on final cleanup.
Co-Authored-By: Claude Opus 4.7 (1M context) <[email protected]>
---
.../evas/engines/gl_common/evas_gl_common.h | 9 +-
.../evas/engines/gl_common/evas_gl_context.c | 10 +-
.../engines/gl_generic/Evas_Engine_GL_Generic.h | 6 +
.../engines/gl_generic/evas_ector_gl_span_shader.c | 82 +++++----
src/modules/evas/engines/gl_generic/evas_engine.c | 204 ++++++++++-----------
5 files changed, 159 insertions(+), 152 deletions(-)
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 f82d2bb980..d499819c6d 100644
--- a/src/modules/evas/engines/gl_common/evas_gl_common.h
+++ b/src/modules/evas/engines/gl_common/evas_gl_common.h
@@ -285,7 +285,7 @@ typedef struct _Span_Channel_Params {
/* Gradient parameters (unused for Solid type) */
float grad_a, grad_b, grad_c; /* linear: t = a*px + b*py + c */
int grad_spread; /* 0=PAD, 1=REFLECT, 2=REPEAT */
- GLuint grad_ramp; /* 1024x1 ramp texture */
+ float grad_ramp_y; /* atlas V coordinate: (row+0.5)/SPAN_GRAD_ATLAS_H */
int grad_type; /* 0=linear, 1=radial */
float grad_d, grad_e, grad_f; /* radial: 2nd affine row */
float grad_ra, grad_rdx, grad_rdy; /* radial: quadratic params */
@@ -298,6 +298,8 @@ typedef struct _Span_Pipe_Params {
int x, y, w, h; /* draw rect in canvas space */
uint32_t mul_col; /* multiply color */
float fbo_off_x, fbo_off_y; /* atlas FBO sub-region offset */
+ /* Gradient ramp atlas (0 = atlas unavailable, gradient shapes are skipped) */
+ GLuint grad_atlas_tex; /* GL texture name of the shared gradient ramp atlas */
/* Composite mask parameters (0/NULL = no mask) */
GLuint mask_tex; /* GL texture name of mask FBO (0 = no mask) */
int comp_method; /* Efl_Gfx_Vg_Composite_Method */
@@ -391,7 +393,7 @@ struct _Evas_Engine_GL_Context
float span_fill_grad_b; /* y coefficient for fill gradient t */
float span_fill_grad_c; /* constant term for fill gradient t */
int span_fill_grad_spread; /* 0=PAD 1=REFLECT 2=REPEAT */
- GLuint span_fill_grad_ramp; /* 1024×1 gradient ramp GL texture name */
+ float span_fill_grad_ramp_y; /* atlas V coord: (row+0.5)/SPAN_GRAD_ATLAS_H */
int span_fill_grad_type; /* 0=linear, 1=radial */
float span_fill_grad_d; /* radial: 2nd affine row x coeff */
float span_fill_grad_e; /* radial: 2nd affine row y coeff */
@@ -403,7 +405,7 @@ struct _Evas_Engine_GL_Context
float span_stroke_grad_b;
float span_stroke_grad_c;
int span_stroke_grad_spread;
- GLuint span_stroke_grad_ramp;
+ float span_stroke_grad_ramp_y; /* atlas V coord: (row+0.5)/SPAN_GRAD_ATLAS_H */
int span_stroke_grad_type;
float span_stroke_grad_d;
float span_stroke_grad_e;
@@ -411,6 +413,7 @@ struct _Evas_Engine_GL_Context
float span_stroke_grad_ra;
float span_stroke_grad_rdx;
float span_stroke_grad_rdy;
+ GLuint span_grad_atlas_tex; /* gradient ramp atlas GL texture name */
float span_fbo_off_x; /* atlas FBO sub-region x offset */
float span_fbo_off_y; /* atlas FBO sub-region y offset */
int span_fill_x_min; /* spatial split: fill texture x_min */
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 d60ce6cf54..59c6ff39b0 100644
--- a/src/modules/evas/engines/gl_common/evas_gl_context.c
+++ b/src/modules/evas/engines/gl_common/evas_gl_context.c
@@ -2125,7 +2125,7 @@ evas_gl_common_context_span_push(Evas_Engine_GL_Context *gc,
_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 == p->fill.grad_ramp &&
+ _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 &&
@@ -2137,7 +2137,7 @@ evas_gl_common_context_span_push(Evas_Engine_GL_Context *gc,
_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 == p->stroke.grad_ramp &&
+ _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 &&
@@ -2149,6 +2149,7 @@ evas_gl_common_context_span_push(Evas_Engine_GL_Context *gc,
_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 &&
@@ -2201,7 +2202,7 @@ evas_gl_common_context_span_push(Evas_Engine_GL_Context *gc,
_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 = p->fill.grad_ramp;
+ _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;
@@ -2214,7 +2215,7 @@ evas_gl_common_context_span_push(Evas_Engine_GL_Context *gc,
_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 = p->stroke.grad_ramp;
+ _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;
@@ -2228,6 +2229,7 @@ evas_gl_common_context_span_push(Evas_Engine_GL_Context *gc,
_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;
diff --git a/src/modules/evas/engines/gl_generic/Evas_Engine_GL_Generic.h b/src/modules/evas/engines/gl_generic/Evas_Engine_GL_Generic.h
index d48b82199b..7872dda4b9 100644
--- a/src/modules/evas/engines/gl_generic/Evas_Engine_GL_Generic.h
+++ b/src/modules/evas/engines/gl_generic/Evas_Engine_GL_Generic.h
@@ -7,6 +7,7 @@
#include "../gl_common/evas_gl_common.h"
#include "../gl_common/evas_gl_core.h"
#include "../gl_common/evas_gl_core_private.h"
+#include "evas_ector_gl_grad_atlas.h"
typedef struct _Render_Engine_GL_Generic Render_Engine_GL_Generic;
typedef struct _Render_Output_GL_Generic Render_Output_GL_Generic;
@@ -24,6 +25,11 @@ struct _Render_Engine_GL_Generic
Render_Output_GL_Generic *current;
+ /* Gradient ramp atlas: one 1024×64 RGBA8 texture pool per engine lifetime.
+ * NULL if span_grad_atlas_new() failed — gradient shapes are skipped per
+ * the spec error table (one-shot ERR logged at allocation time). */
+ Span_Grad_Atlas *grad_atlas;
+
struct {
Evas_Object_Image_Pixels_Get_Cb get_pixels;
void *get_pixels_data;
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 3574d31bca..8cf41d4a04 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
@@ -24,6 +24,7 @@
#include "evas_ector_log_restore.h"
#include "evas_ector_gl_span.h"
+#include "evas_ector_gl_grad_atlas.h"
/* ------------------------------------------------------------------ */
/* GLSL shader source strings */
@@ -68,6 +69,7 @@ static const char _glsl_uniforms_shared[] =
"uniform highp vec2 u_fbo_offset;\n"
"uniform highp float u_inv_tw;\n"
"uniform highp float u_inv_th;\n"
+ "uniform sampler2D u_grad_ramp_atlas;\n"
"#define MAX_SPANS 64\n";
/* Fill-and-stroke binding set: both span samplers + both offsets. */
@@ -136,14 +138,15 @@ static const char _glsl_uniforms_solid_s[] =
* large surfaces) and are squared in the radial path, so mediump (fp16)
* would lose precision in the gradient parameter. */
-/* Fill-and-stroke gradient: both ramp samplers + all coefficients. */
+/* Fill-and-stroke gradient: ramp_y uniforms + all coefficients.
+ * The ramp atlas sampler (u_grad_ramp_atlas) is now in _glsl_uniforms_shared. */
static const char _glsl_uniforms_gradient_fs[] =
- "uniform sampler2D u_fill_grad_ramp;\n"
+ "uniform highp float u_fill_grad_ramp_y;\n"
"uniform highp float u_fill_grad_a;\n"
"uniform highp float u_fill_grad_b;\n"
"uniform highp float u_fill_grad_c;\n"
"uniform int u_fill_grad_spread;\n"
- "uniform sampler2D u_stroke_grad_ramp;\n"
+ "uniform highp float u_stroke_grad_ramp_y;\n"
"uniform highp float u_stroke_grad_a;\n"
"uniform highp float u_stroke_grad_b;\n"
"uniform highp float u_stroke_grad_c;\n"
@@ -163,13 +166,12 @@ static const char _glsl_uniforms_gradient_fs[] =
"uniform highp float u_stroke_grad_rdx;\n"
"uniform highp float u_stroke_grad_rdy;\n";
-/* Fill-only gradient: fill ramp + fill coefficients.
+/* Fill-only gradient: fill ramp_y + fill coefficients.
* Stroke uniforms are also declared so dead-branch identifiers resolve on
* strict GLSL ES 2.0 front-ends (V3D 4.2). "#define u_has_stroke 0" in
- * _glsl_uniforms_bind_f makes the stroke branch statically dead; post-parse
- * DCE then removes the texture2D(u_stroke_grad_ramp, ...) dispatch. */
+ * _glsl_uniforms_bind_f makes the stroke branch statically dead. */
static const char _glsl_uniforms_gradient_f[] =
- "uniform sampler2D u_fill_grad_ramp;\n"
+ "uniform highp float u_fill_grad_ramp_y;\n"
"uniform highp float u_fill_grad_a;\n"
"uniform highp float u_fill_grad_b;\n"
"uniform highp float u_fill_grad_c;\n"
@@ -181,7 +183,7 @@ static const char _glsl_uniforms_gradient_f[] =
"uniform highp float u_fill_grad_ra;\n"
"uniform highp float u_fill_grad_rdx;\n"
"uniform highp float u_fill_grad_rdy;\n"
- "uniform sampler2D u_stroke_grad_ramp;\n"
+ "uniform highp float u_stroke_grad_ramp_y;\n"
"uniform highp float u_stroke_grad_a;\n"
"uniform highp float u_stroke_grad_b;\n"
"uniform highp float u_stroke_grad_c;\n"
@@ -194,13 +196,12 @@ static const char _glsl_uniforms_gradient_f[] =
"uniform highp float u_stroke_grad_rdx;\n"
"uniform highp float u_stroke_grad_rdy;\n";
-/* Stroke-only gradient: stroke ramp + stroke coefficients.
+/* Stroke-only gradient: stroke ramp_y + stroke coefficients.
* Fill uniforms are also declared so dead-branch identifiers resolve on
* strict GLSL ES 2.0 front-ends (V3D 4.2). "#define u_has_fill 0" in
- * _glsl_uniforms_bind_s makes the fill branch statically dead; post-parse
- * DCE then removes the texture2D(u_fill_grad_ramp, ...) dispatch. */
+ * _glsl_uniforms_bind_s makes the fill branch statically dead. */
static const char _glsl_uniforms_gradient_s[] =
- "uniform sampler2D u_stroke_grad_ramp;\n"
+ "uniform highp float u_stroke_grad_ramp_y;\n"
"uniform highp float u_stroke_grad_a;\n"
"uniform highp float u_stroke_grad_b;\n"
"uniform highp float u_stroke_grad_c;\n"
@@ -212,7 +213,7 @@ static const char _glsl_uniforms_gradient_s[] =
"uniform highp float u_stroke_grad_ra;\n"
"uniform highp float u_stroke_grad_rdx;\n"
"uniform highp float u_stroke_grad_rdy;\n"
- "uniform sampler2D u_fill_grad_ramp;\n"
+ "uniform highp float u_fill_grad_ramp_y;\n"
"uniform highp float u_fill_grad_a;\n"
"uniform highp float u_fill_grad_b;\n"
"uniform highp float u_fill_grad_c;\n"
@@ -303,10 +304,11 @@ static const char _glsl_grad_spread[] =
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"
- " * blend into result. */\n"
+ " * (linear or radial) and sample the gradient ramp atlas at ramp_v,\n"
+ " * then src-over blend into result. */\n"
"vec4 scan_gradient_spans(sampler2D span_tex, highp vec2 off,\n"
- " sampler2D ramp, highp float ga, highp float gb, highp float gc,\n"
+ " highp float ramp_v,\n"
+ " highp float ga, highp float gb, highp float gc,\n"
" int gspread, int gtype,\n"
" highp float gd, highp float ge, highp float gf,\n"
" highp float gra, highp float grdx, highp float grdy,\n"
@@ -342,7 +344,7 @@ static const char _glsl_scan_gradient_spans[] =
" t = ga * px + gb * py + gc;\n"
" }\n"
" t = grad_spread(t, gspread);\n"
- " vec4 grad_col = texture2D(ramp, vec2(t, 0.5));\n"
+ " vec4 grad_col = texture2D(u_grad_ramp_atlas, vec2(t, ramp_v));\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"
@@ -384,7 +386,7 @@ static const char _glsl_main_gradient_body[] =
" highp 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_ramp_y,\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"
@@ -395,7 +397,7 @@ static const char _glsl_main_gradient_body[] =
" highp 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_ramp_y,\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"
@@ -522,12 +524,13 @@ typedef struct
int loc_fill_x_min;
int loc_stroke_x_min;
/* Gradient-only uniforms (location -1 in solid shader → safe no-op) */
- int loc_fill_grad_ramp;
+ int loc_grad_ramp_atlas; /* u_grad_ramp_atlas — shared atlas sampler */
+ int loc_fill_grad_ramp_y; /* u_fill_grad_ramp_y — atlas V coord for fill */
int loc_fill_grad_a;
int loc_fill_grad_b;
int loc_fill_grad_c;
int loc_fill_grad_spread;
- int loc_stroke_grad_ramp;
+ int loc_stroke_grad_ramp_y; /* u_stroke_grad_ramp_y — atlas V coord for stroke */
int loc_stroke_grad_a;
int loc_stroke_grad_b;
int loc_stroke_grad_c;
@@ -675,12 +678,13 @@ _link_program(Span_Shader *ss, const char **frag_parts, int frag_count)
ss->loc_fill_x_min = glGetUniformLocation(ss->program, "u_fill_x_min");
ss->loc_stroke_x_min = glGetUniformLocation(ss->program, "u_stroke_x_min");
/* Gradient uniforms — location -1 in solid shader (safe no-op). */
- ss->loc_fill_grad_ramp = glGetUniformLocation(ss->program, "u_fill_grad_ramp");
- ss->loc_fill_grad_a = glGetUniformLocation(ss->program, "u_fill_grad_a");
- ss->loc_fill_grad_b = glGetUniformLocation(ss->program, "u_fill_grad_b");
- ss->loc_fill_grad_c = glGetUniformLocation(ss->program, "u_fill_grad_c");
- ss->loc_fill_grad_spread = glGetUniformLocation(ss->program, "u_fill_grad_spread");
- ss->loc_stroke_grad_ramp = glGetUniformLocation(ss->program, "u_stroke_grad_ramp");
+ ss->loc_grad_ramp_atlas = glGetUniformLocation(ss->program, "u_grad_ramp_atlas");
+ ss->loc_fill_grad_ramp_y = glGetUniformLocation(ss->program, "u_fill_grad_ramp_y");
+ ss->loc_fill_grad_a = glGetUniformLocation(ss->program, "u_fill_grad_a");
+ ss->loc_fill_grad_b = glGetUniformLocation(ss->program, "u_fill_grad_b");
+ ss->loc_fill_grad_c = glGetUniformLocation(ss->program, "u_fill_grad_c");
+ ss->loc_fill_grad_spread = glGetUniformLocation(ss->program, "u_fill_grad_spread");
+ ss->loc_stroke_grad_ramp_y = glGetUniformLocation(ss->program, "u_stroke_grad_ramp_y");
ss->loc_stroke_grad_a = glGetUniformLocation(ss->program, "u_stroke_grad_a");
ss->loc_stroke_grad_b = glGetUniformLocation(ss->program, "u_stroke_grad_b");
ss->loc_stroke_grad_c = glGetUniformLocation(ss->program, "u_stroke_grad_c");
@@ -1153,34 +1157,36 @@ _span_draw_pass(Span_Shader *ss,
glUniform1i(ss->loc_fill_x_min, gc->pipe[pipe_idx].shader.span_fill_x_min);
glUniform1i(ss->loc_stroke_x_min, gc->pipe[pipe_idx].shader.span_stroke_x_min);
- /* Gradient parameters — units 2 and 3 for fill and stroke ramp textures.
+ /* Gradient parameters — atlas on texture unit 2, ramp_y uniforms per shape.
* Only set these uniforms when using a gradient shader; on the solid fast
* path all gradient uniform locations are -1 and glUniform on -1 is a GL
* no-op per spec, but the calls are still dispatched through the driver.
* Skipping them entirely saves ~22 glUniform calls per solid draw.
*
- * Identify gradient shaders by checking loc_fill_grad_ramp: it is >= 0
+ * Identify gradient shaders by checking loc_grad_ramp_atlas: it is >= 0
* only in gradient-family shaders (solid shaders have no such uniform). */
- if (ss->loc_fill_grad_ramp >= 0 || ss->loc_stroke_grad_ramp >= 0)
+ if (ss->loc_grad_ramp_atlas >= 0)
{
- GLuint fill_ramp = gc->pipe[pipe_idx].shader.span_fill_grad_ramp;
- GLuint stroke_ramp = gc->pipe[pipe_idx].shader.span_stroke_grad_ramp;
+ GLuint atlas_tex = gc->pipe[pipe_idx].shader.span_grad_atlas_tex;
+
+ /* Bind shared gradient ramp atlas on texture unit 2. */
+ glActiveTexture(GL_TEXTURE2);
+ glBindTexture(GL_TEXTURE_2D, atlas_tex);
+ glUniform1i(ss->loc_grad_ramp_atlas, 2);
+
+ /* Per-shape ramp V coordinates. */
+ glUniform1f(ss->loc_fill_grad_ramp_y, gc->pipe[pipe_idx].shader.span_fill_grad_ramp_y);
+ glUniform1f(ss->loc_stroke_grad_ramp_y, gc->pipe[pipe_idx].shader.span_stroke_grad_ramp_y);
glUniform1f(ss->loc_fill_grad_a, gc->pipe[pipe_idx].shader.span_fill_grad_a);
glUniform1f(ss->loc_fill_grad_b, gc->pipe[pipe_idx].shader.span_fill_grad_b);
glUniform1f(ss->loc_fill_grad_c, gc->pipe[pipe_idx].shader.span_fill_grad_c);
glUniform1i(ss->loc_fill_grad_spread, gc->pipe[pipe_idx].shader.span_fill_grad_spread);
- glUniform1i(ss->loc_fill_grad_ramp, 2); /* texture unit 2 */
- glActiveTexture(GL_TEXTURE2);
- glBindTexture(GL_TEXTURE_2D, fill_ramp ? fill_ramp : 0);
glUniform1f(ss->loc_stroke_grad_a, gc->pipe[pipe_idx].shader.span_stroke_grad_a);
glUniform1f(ss->loc_stroke_grad_b, gc->pipe[pipe_idx].shader.span_stroke_grad_b);
glUniform1f(ss->loc_stroke_grad_c, gc->pipe[pipe_idx].shader.span_stroke_grad_c);
glUniform1i(ss->loc_stroke_grad_spread, gc->pipe[pipe_idx].shader.span_stroke_grad_spread);
- glUniform1i(ss->loc_stroke_grad_ramp, 3); /* texture unit 3 */
- glActiveTexture(GL_TEXTURE3);
- glBindTexture(GL_TEXTURE_2D, stroke_ramp ? stroke_ramp : 0);
glUniform1i(ss->loc_fill_grad_type, gc->pipe[pipe_idx].shader.span_fill_grad_type);
glUniform1f(ss->loc_fill_grad_d, gc->pipe[pipe_idx].shader.span_fill_grad_d);
diff --git a/src/modules/evas/engines/gl_generic/evas_engine.c b/src/modules/evas/engines/gl_generic/evas_engine.c
index 18d9e71a8e..c7352e35ca 100644
--- a/src/modules/evas/engines/gl_generic/evas_engine.c
+++ b/src/modules/evas/engines/gl_generic/evas_engine.c
@@ -159,6 +159,10 @@ eng_engine_new(void)
if (!engine) return NULL;
engine->software.surface_cache = generic_cache_new(engine, eng_image_free);
+ /* Gradient ramp atlas: NULL return means atlas unavailable — gradient
+ * shapes will be skipped per the spec error table (no-op, non-fatal). */
+ engine->grad_atlas = span_grad_atlas_new();
+
return engine;
}
@@ -170,6 +174,8 @@ eng_engine_free(void *engine)
generic_cache_destroy(e->software.surface_cache);
+ if (e->grad_atlas) span_grad_atlas_free(e->grad_atlas);
+
EINA_LIST_FREE(e->software.outputs, output)
ERR("Output %p not properly cleaned before engine destruction.", output);
free(e);
@@ -2818,89 +2824,9 @@ eng_ector_begin(void *engine, void *surface,
}
/* ------------------------------------------------------------------ */
-/* Gradient ramp upload + t-coefficient helpers for eng_ector_end() */
+/* Gradient t-coefficient helpers for eng_ector_end() */
/* ------------------------------------------------------------------ */
-/**
- * Ensure the 1024×1 RGBA8 gradient ramp texture is uploaded to the GPU.
- *
- * Caches the GL texture object in sc->grad_ramp_tex across frames.
- * The ramp content is always re-uploaded every frame (4KB via
- * glTexSubImage2D) to avoid stale data when gradient stops are animated.
- *
- * @param sc Span_Collector owning the ramp cache slot.
- * @return GL texture name, or 0 on failure.
- */
-static GLuint
-_span_gradient_upload_ramp(Span_Collector *sc)
-{
- Ector_Renderer_Software_Gradient_Data *gd;
- GLuint ramp_tex;
- uint32_t crc;
-
- if (!sc || !sc->gradient_data) return 0;
- gd = (Ector_Renderer_Software_Gradient_Data *)sc->gradient_data;
- if (!gd->color_table || gd->ctable_status != CTABLE_READY_DONE) return 0;
-
- /* Quick CRC over the 1024-entry color table to detect changes.
- * eina_crc hashes 4KB — cheap compared to the glTexSubImage2D it gates. */
- crc = eina_crc((const char *)gd->color_table,
- 1024 * sizeof(uint32_t), 0xFFFFFFFF, EINA_TRUE);
-
- if (sc->grad_ramp_tex && crc == sc->grad_ramp_crc)
- return (GLuint)sc->grad_ramp_tex;
-
- /* Build staging buffer: swap R<->B for GL_RGBA upload. */
- {
- uint32_t staging[1024];
- int j;
- for (j = 0; j < 1024; j++)
- {
- uint32_t c = gd->color_table[j];
- uint8_t *p = (uint8_t *)&staging[j];
- p[0] = (c >> 16) & 0xFF; /* R */
- p[1] = (c >> 8) & 0xFF; /* G */
- p[2] = c & 0xFF; /* B */
- p[3] = (c >> 24) & 0xFF; /* A */
- }
-
- if (!sc->grad_ramp_tex)
- {
- glGenTextures(1, &ramp_tex);
- if (!ramp_tex) return 0;
-
- glBindTexture(GL_TEXTURE_2D, ramp_tex);
- glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST);
- glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST);
- glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE);
- glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);
- glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, 1024, 1, 0,
- GL_RGBA, GL_UNSIGNED_BYTE, staging);
- /* Unbind to 0 rather than restoring gc->state.current.cur_tex:
- * gc is not in scope here. This is safe because
- * span_shader_pipe_flush sets gc->state.current.cur_tex = 0
- * via the state invalidation path before any subsequent Evas
- * texture operations, so the state cache remains coherent. */
- glBindTexture(GL_TEXTURE_2D, 0);
-
- sc->grad_ramp_tex = (unsigned int)ramp_tex;
- }
- else
- {
- ramp_tex = (GLuint)sc->grad_ramp_tex;
- glBindTexture(GL_TEXTURE_2D, ramp_tex);
- glTexSubImage2D(GL_TEXTURE_2D, 0, 0, 0, 1024, 1,
- GL_RGBA, GL_UNSIGNED_BYTE, staging);
- /* Same rationale as above: unbind to 0, coherence restored by
- * span_shader_pipe_flush state invalidation. */
- glBindTexture(GL_TEXTURE_2D, 0);
- }
- }
-
- sc->grad_ramp_crc = crc;
- return (GLuint)sc->grad_ramp_tex;
-}
-
/**
* Compute linear gradient t-coefficients for the GL shader.
*
@@ -3000,19 +2926,23 @@ _span_gradient_radial_coeffs(Ector_Renderer_Software_Gradient_Data *gd,
* 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 atlas Gradient ramp atlas (may be NULL → gradient skipped).
* @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_gramp_y Atlas V coordinate for this gradient's ramp row.
+ * @param out_atlas_skip Set to EINA_TRUE if atlas lookup failed (skip shape).
* @param out_gtype 0=linear, 1=radial.
*/
static void
_compute_gradient_coeffs(Span_Collector *sc,
+ Span_Grad_Atlas *atlas,
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,
+ int *out_gs, float *out_gramp_y, Eina_Bool *out_atlas_skip,
+ int *out_gtype,
float *out_gd, float *out_ge, float *out_gf,
float *out_gra, float *out_grdx, float *out_grdy)
{
@@ -3023,7 +2953,43 @@ _compute_gradient_coeffs(Span_Collector *sc,
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);
+
+ /* Upload ramp into atlas and get V coordinate.
+ * version is content-derived (hash of the 4 KB staging buffer) so the
+ * identity fast path in span_grad_atlas_lookup is keyed on (grad_id,
+ * content_hash) — animated stop changes force a re-lookup automatically.
+ * Fallback: if atlas is unavailable, mark the shape as atlas-skip. */
+ if (atlas && gd->color_table && gd->ctable_status == CTABLE_READY_DONE)
+ {
+ uint32_t staging[1024];
+ int j;
+ for (j = 0; j < 1024; j++)
+ {
+ uint32_t c = gd->color_table[j];
+ uint8_t *p = (uint8_t *)&staging[j];
+ p[0] = (c >> 16) & 0xFF; /* R */
+ p[1] = (c >> 8) & 0xFF; /* G */
+ p[2] = c & 0xFF; /* B */
+ p[3] = (c >> 24) & 0xFF; /* A */
+ }
+ uint32_t version = span_grad_atlas_hash((const uint8_t *)staging);
+ int row = span_grad_atlas_lookup(atlas, sc->gradient_data,
+ version,
+ (const uint8_t *)staging);
+ if (row < 0)
+ {
+ /* Atlas disabled or full — skip this shape. */
+ *out_atlas_skip = EINA_TRUE;
+ return;
+ }
+ *out_gramp_y = span_grad_atlas_row_to_v(row);
+ }
+ else
+ {
+ /* Atlas unavailable — skip gradient shapes (spec error table). */
+ *out_atlas_skip = EINA_TRUE;
+ return;
+ }
if (shader_type == (int)LinearGradient)
{
@@ -3105,6 +3071,9 @@ eng_ector_end(void *engine,
evas_gl_common_context_target_surface_set(gc, glim);
+ /* Bump gradient atlas LRU frame counter for this render pass. */
+ span_grad_atlas_frame_begin(((Render_Engine_GL_Generic *)engine)->grad_atlas);
+
/* Atlas offset: when VG surfaces share an FBO via the texture
* atlas pool, each surface occupies a sub-rectangle at (ox, oy).
* Dedicated FBOs have ox=oy=0 (no offset). */
@@ -3191,33 +3160,46 @@ eng_ector_end(void *engine,
/* Per-shape gradient coefficients. */
float fill_ga = 0.0f, fill_gb = 0.0f, fill_gc_coef = 0.0f;
int fill_gs = 0;
- GLuint fill_gramp = 0;
+ float fill_gramp_y = 0.0f;
+ Eina_Bool fill_atlas_skip = EINA_FALSE;
int fill_gtype = 0;
float fill_gd = 0.0f, fill_ge = 0.0f, fill_gf = 0.0f;
float fill_gra = 0.0f, fill_grdx = 0.0f, fill_grdy = 0.0f;
float stroke_ga = 0.0f, stroke_gb = 0.0f, stroke_gc_coef = 0.0f;
int stroke_gs = 0;
- GLuint stroke_gramp = 0;
+ float stroke_gramp_y = 0.0f;
+ Eina_Bool stroke_atlas_skip = EINA_FALSE;
int stroke_gtype = 0;
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 (_rsd)
- {
- _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);
- }
+ {
+ Render_Engine_GL_Generic *re =
+ (Render_Engine_GL_Generic *)engine;
+ Span_Grad_Atlas *atlas = re->grad_atlas;
+
+ if (_rsd)
+ {
+ _compute_gradient_coeffs(sc_fill, atlas,
+ &fill_shader_type, &fill_col,
+ &fill_ga, &fill_gb, &fill_gc_coef,
+ &fill_gs, &fill_gramp_y,
+ &fill_atlas_skip, &fill_gtype,
+ &fill_gd, &fill_ge, &fill_gf,
+ &fill_gra, &fill_grdx, &fill_grdy);
+ _compute_gradient_coeffs(sc_stroke, atlas,
+ &stroke_shader_type, &stroke_col,
+ &stroke_ga, &stroke_gb, &stroke_gc_coef,
+ &stroke_gs, &stroke_gramp_y,
+ &stroke_atlas_skip, &stroke_gtype,
+ &stroke_gd, &stroke_ge, &stroke_gf,
+ &stroke_gra, &stroke_grdx, &stroke_grdy);
+ }
+
+ /* Skip gradient shapes when atlas lookup failed. */
+ if (fill_atlas_skip || stroke_atlas_skip) continue;
+ }
/* Draw each spatial-split texture within this shape.
* Most collectors have 1 texture; complex shapes that
@@ -3263,6 +3245,14 @@ eng_ector_end(void *engine,
_spp.fbo_off_x = (float)ox;
_spp.fbo_off_y = (float)oy;
+ {
+ Render_Engine_GL_Generic *re =
+ (Render_Engine_GL_Generic *)engine;
+ _spp.grad_atlas_tex =
+ (re->grad_atlas && re->grad_atlas->tex)
+ ? re->grad_atlas->tex : 0;
+ }
+
_spp.fill.tex = f_tex;
_spp.fill.off_tx = (float)f_tx;
_spp.fill.off_ty = (float)f_ty;
@@ -3272,9 +3262,9 @@ eng_ector_end(void *engine,
_spp.fill.grad_a = fill_ga;
_spp.fill.grad_b = fill_gb;
_spp.fill.grad_c = fill_gc_coef;
- _spp.fill.grad_spread = fill_gs;
- _spp.fill.grad_ramp = fill_gramp;
- _spp.fill.grad_type = fill_gtype;
+ _spp.fill.grad_spread = fill_gs;
+ _spp.fill.grad_ramp_y = fill_gramp_y;
+ _spp.fill.grad_type = fill_gtype;
_spp.fill.grad_d = fill_gd;
_spp.fill.grad_e = fill_ge;
_spp.fill.grad_f = fill_gf;
@@ -3291,9 +3281,9 @@ eng_ector_end(void *engine,
_spp.stroke.grad_a = stroke_ga;
_spp.stroke.grad_b = stroke_gb;
_spp.stroke.grad_c = stroke_gc_coef;
- _spp.stroke.grad_spread = stroke_gs;
- _spp.stroke.grad_ramp = stroke_gramp;
- _spp.stroke.grad_type = stroke_gtype;
+ _spp.stroke.grad_spread = stroke_gs;
+ _spp.stroke.grad_ramp_y = stroke_gramp_y;
+ _spp.stroke.grad_type = stroke_gtype;
_spp.stroke.grad_d = stroke_gd;
_spp.stroke.grad_e = stroke_ge;
_spp.stroke.grad_f = stroke_gf;
--
To stop receiving notification emails like this one, please contact
the administrator of this repository.