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 741122e98a358427ca9439a457dc11bc508ed427
Author: Cedric BAIL <[email protected]>
AuthorDate: Thu Aug 13 22:54:22 2026 -0600
perf(evas_ector_gl): collapse per-draw span state into a VAO
Counting GL calls with an LD_PRELOAD interposer on expedite test 125
"VG Scaled" (128 VG objects, all resizing every frame) showed
span_shader_pipe_flush() issuing 38 state calls for every one of the
255 span draws in a frame:
glVertexAttribPointer 2549
glEnableVertexAttribArray 2423
glUniform1i / glUniform1f 1275
glActiveTexture 1020
glBindTexture 1276
glBufferData 255
Three changes remove most of that:
- Attribute locations are now bound with glBindAttribLocation before
linking, to the fixed SPAN_ATTR_* indices, instead of being queried
afterwards. All twelve programs then agree on where a given semantic
lives, which is the precondition for sharing vertex array state whose
keys are locations rather than programs. Solid and gradient variants
deliberately overlap on 5 and 6; a VAO is per-variant and no variant
declares both sets. The now-dead attr_* fields leave Span_Shader.
- One streaming VBO replaces the per-pipe-entry buffers. A VAO records
which buffer each attribute reads from, so a single shared buffer is
what lets the VAO be per-variant rather than per-pipe.
- A VAO per Span_Variant, created on first use, replaces the ten
glVertexAttribPointer plus ten glEnableVertexAttribArray calls that
the flush used to issue per draw. Vertex array objects are probed
through dlsym (core name first, then the OES alias); the previous
per-draw attribute setup is kept as the fallback when they are
absent, so GLES2 devices without GL_OES_vertex_array_object are
unaffected.
Sampler uniforms are also assigned once per program rather than per
draw: the texture unit for each sampler is program state and never
changes.
glVertexAttribPointer 2549 -> 257
glEnableVertexAttribArray 2423 -> 130
glUniform1i / glUniform1f 1275 -> 510
Be clear about what this does and does not buy. On the machine it was
measured on - Intel UHD 620, Mesa 26.1.6/iris - it is wall-clock
neutral: over four interleaved runs of 400 frames each, after
discarding warm-up, test 125 moves 68.5 to 68.8 FPS and test 121 (VG
Basic Batman) 75.6 to 76.8. Those entry points are close to free in
Mesa, where glVertexAttribPointer only sets a dirty bit. The change is
kept because the span path also targets devices where they are not -
the V3D 4.2 and Mali paths this shader already carries workarounds for
- and because sharing one VBO and fixing the attribute locations is
simpler than what it replaces.
An earlier revision also cached the bound program and textures across
flushes. It was dropped: the cache had to be invalidated whenever
shader_array_flush restarted, and a VG render pass holds only about
two span pipes with a different program each, so it almost never hit
while adding heuristic invalidation to get wrong.
All ten VG expedite tests render byte-identical frames; ector-suite
and evas-suite pass.
Co-Authored-By: Claude Opus 5 (1M context) <[email protected]>
---
.../engines/gl_generic/evas_ector_gl_span_shader.c | 384 ++++++++++++++-------
1 file changed, 260 insertions(+), 124 deletions(-)
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 fc87ce106c..e8330cd0f3 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
@@ -16,6 +16,7 @@
# include "config.h"
#endif
+#include <dlfcn.h>
#include <stddef.h>
#include <stdint.h>
#include <stdlib.h>
@@ -679,6 +680,30 @@ _span_vs_parts_build(int kind, int mask, int *out_count)
}
}
+/* ------------------------------------------------------------------ */
+/* Fixed vertex attribute locations */
+/* ------------------------------------------------------------------ */
+
+/* Bound with glBindAttribLocation before every link so that all twelve
+ * programs agree. Solid and gradient variants reuse 5 and 6 because a VAO
+ * is created per variant and no variant declares both sets. The widest
+ * variant (gradient + mask) uses 13 of the 16 GLES2-guaranteed slots. */
+#define SPAN_ATTR_POSITION 0
+#define SPAN_ATTR_FBO_FILL_OFF 1
+#define SPAN_ATTR_STROKE_OFF_FLAGS 2
+#define SPAN_ATTR_X_MIN 3
+#define SPAN_ATTR_MUL_COL 4
+#define SPAN_ATTR_FILL_COL 5 /* solid variants */
+#define SPAN_ATTR_STROKE_COL 6 /* solid variants */
+#define SPAN_ATTR_F_GRAD_ABC_Y 5 /* gradient variants */
+#define SPAN_ATTR_F_GRAD_DEF 6
+#define SPAN_ATTR_F_GRAD_RADIAL 7
+#define SPAN_ATTR_S_GRAD_ABC_Y 8
+#define SPAN_ATTR_S_GRAD_DEF 9
+#define SPAN_ATTR_S_GRAD_RADIAL 10
+#define SPAN_ATTR_MASK_OFF_SIZE 11
+#define SPAN_ATTR_MASK_COMP_INV 12
+
/* ------------------------------------------------------------------ */
/* Internal shader state */
/* ------------------------------------------------------------------ */
@@ -687,7 +712,8 @@ typedef struct
{
unsigned int program;
/* Uniform locations — samplers and pool reciprocals only.
- * Per-shape data is now in vertex attributes (attr_* below). */
+ * Per-shape data travels in vertex attributes, whose locations are the
+ * fixed SPAN_ATTR_* constants bound before linking. */
int loc_fill_spans;
int loc_stroke_spans;
int loc_inv_tw;
@@ -696,23 +722,8 @@ typedef struct
int loc_grad_ramp_atlas;
/* Mask sampler — valid only in mask variants (-1 otherwise). */
int loc_mask_tex;
- /* Attribute locations — queried after glLinkProgram.
- * -1 for attributes absent in this variant; BIND_ATTR skips them. */
- int attr_position;
- int attr_fbo_fill_off;
- int attr_stroke_off_flags;
- int attr_x_min;
- int attr_mul_col;
- int attr_fill_col; /* solid variants only; -1 in gradient */
- int attr_stroke_col; /* solid variants only; -1 in gradient */
- int attr_fill_grad_abc_y; /* gradient variants only */
- int attr_fill_grad_def;
- int attr_fill_grad_radial;
- int attr_stroke_grad_abc_y;
- int attr_stroke_grad_def;
- int attr_stroke_grad_radial;
- int attr_mask_off_size; /* mask variants only */
- int attr_mask_comp_inv; /* mask variants only */
+ /* Sampler uniforms are program state; assign the texture units once. */
+ Eina_Bool samplers_bound;
} Span_Shader;
/* [kind][bind][mask] — kind 0=solid 1=gradient, bind in Span_Bind_Set, mask 0/1.
@@ -741,6 +752,157 @@ static Span_Shader_State _span_shader_state = SPAN_SHADER_UNTRIED;
* normal rendering (non-mask shaders have no mask sampler at all). */
static GLuint _white_mask_tex = 0;
+/* ------------------------------------------------------------------ */
+/* Per-draw state: shared VBO, per-variant VAOs, binding cache */
+/* ------------------------------------------------------------------ */
+
+/* One streaming VBO for every span pipe instead of one per pipe entry.
+ * A VAO records which buffer each attribute reads from, so sharing a single
+ * buffer is what allows the VAO to be per-variant rather than per-pipe. */
+static GLuint _span_vbo = 0;
+
+/* One VAO per Span_Variant, or 0 when vertex array objects are unavailable.
+ * Binding one replaces the ten glVertexAttribPointer + ten
+ * glEnableVertexAttribArray calls the flush used to issue per draw. */
+static GLuint _span_vao[SPAN_VARIANT_COUNT];
+
+static void (*_gl_gen_vao)(GLsizei, GLuint *) = NULL;
+static void (*_gl_bind_vao)(GLuint) = NULL;
+static void (*_gl_del_vao)(GLsizei, const GLuint *) = NULL;
+static int _span_vao_probed = 0;
+
+static void
+_span_vao_probe(void)
+{
+ const char *ext;
+
+ if (_span_vao_probed) return;
+ _span_vao_probed = 1;
+
+ ext = (const char *)glGetString(GL_EXTENSIONS);
+ if (!ext || (!strstr(ext, "GL_OES_vertex_array_object") &&
+ !strstr(ext, "GL_ARB_vertex_array_object")))
+ {
+ /* GLES 3.0+ and desktop GL 3.0+ have it in core with no extension
+ * string in the (non-indexed) list, so fall through to the dlsym
+ * probe rather than giving up here. */
+ }
+
+ _gl_gen_vao = dlsym(RTLD_DEFAULT, "glGenVertexArrays");
+ _gl_bind_vao = dlsym(RTLD_DEFAULT, "glBindVertexArray");
+ _gl_del_vao = dlsym(RTLD_DEFAULT, "glDeleteVertexArrays");
+ if (!_gl_gen_vao || !_gl_bind_vao || !_gl_del_vao)
+ {
+ _gl_gen_vao = dlsym(RTLD_DEFAULT, "glGenVertexArraysOES");
+ _gl_bind_vao = dlsym(RTLD_DEFAULT, "glBindVertexArrayOES");
+ _gl_del_vao = dlsym(RTLD_DEFAULT, "glDeleteVertexArraysOES");
+ }
+ if (!_gl_gen_vao || !_gl_bind_vao || !_gl_del_vao)
+ {
+ _gl_gen_vao = NULL; _gl_bind_vao = NULL; _gl_del_vao = NULL;
+ INF("span shader: no vertex array objects, using per-draw attribute setup");
+ }
+}
+
+/* Describe one attribute: location, component count, byte offset. */
+typedef struct { int loc, cnt, off; } Span_Attr_Desc;
+
+/* Fill @p out with the attribute layout of @p variant; returns the count. */
+static int
+_span_attr_layout(Span_Variant variant, Span_Attr_Desc *out)
+{
+ int n = 0;
+#define A(l, c, o) do { out[n].loc = (l); out[n].cnt = (c); out[n].off = (int)(o); n++; } while (0)
+#define COMMON(base) \
+ A(SPAN_ATTR_POSITION, 2, (base) + offsetof(Span_Vertex_Common, pos)); \
+ A(SPAN_ATTR_FBO_FILL_OFF, 4, (base) + offsetof(Span_Vertex_Common, fbo_fill_off)); \
+ A(SPAN_ATTR_STROKE_OFF_FLAGS, 4, (base) + offsetof(Span_Vertex_Common, stroke_off_flags)); \
+ A(SPAN_ATTR_X_MIN, 2, (base) + offsetof(Span_Vertex_Common, x_min)); \
+ A(SPAN_ATTR_MUL_COL, 4, (base) + offsetof(Span_Vertex_Common, mul_col))
+
+ switch (variant)
+ {
+ case SPAN_VARIANT_SOLID:
+ COMMON(offsetof(Span_Vertex_Solid, c));
+ A(SPAN_ATTR_FILL_COL, 4, offsetof(Span_Vertex_Solid, fill_col));
+ A(SPAN_ATTR_STROKE_COL, 4, offsetof(Span_Vertex_Solid, stroke_col));
+ break;
+ case SPAN_VARIANT_SOLID_MASK:
+ COMMON(offsetof(Span_Vertex_Solid_Mask, s) + offsetof(Span_Vertex_Solid, c));
+ A(SPAN_ATTR_FILL_COL, 4,
+ offsetof(Span_Vertex_Solid_Mask, s) + offsetof(Span_Vertex_Solid, fill_col));
+ A(SPAN_ATTR_STROKE_COL, 4,
+ offsetof(Span_Vertex_Solid_Mask, s) + offsetof(Span_Vertex_Solid, stroke_col));
+ A(SPAN_ATTR_MASK_OFF_SIZE, 4, offsetof(Span_Vertex_Solid_Mask, mask_off_size));
+ A(SPAN_ATTR_MASK_COMP_INV, 2, offsetof(Span_Vertex_Solid_Mask, mask_comp_inv));
+ break;
+ case SPAN_VARIANT_GRADIENT:
+ COMMON(offsetof(Span_Vertex_Gradient, c));
+ A(SPAN_ATTR_F_GRAD_ABC_Y, 4, offsetof(Span_Vertex_Gradient, fill_grad_abc_y));
+ A(SPAN_ATTR_F_GRAD_DEF, 4, offsetof(Span_Vertex_Gradient, fill_grad_def));
+ A(SPAN_ATTR_F_GRAD_RADIAL, 4, offsetof(Span_Vertex_Gradient, fill_grad_radial));
+ A(SPAN_ATTR_S_GRAD_ABC_Y, 4, offsetof(Span_Vertex_Gradient, stroke_grad_abc_y));
+ A(SPAN_ATTR_S_GRAD_DEF, 4, offsetof(Span_Vertex_Gradient, stroke_grad_def));
+ A(SPAN_ATTR_S_GRAD_RADIAL, 4, offsetof(Span_Vertex_Gradient, stroke_grad_radial));
+ break;
+ case SPAN_VARIANT_GRADIENT_MASK:
+ COMMON(offsetof(Span_Vertex_Gradient_Mask, g) + offsetof(Span_Vertex_Gradient, c));
+#define G(f) (offsetof(Span_Vertex_Gradient_Mask, g) + offsetof(Span_Vertex_Gradient, f))
+ A(SPAN_ATTR_F_GRAD_ABC_Y, 4, G(fill_grad_abc_y));
+ A(SPAN_ATTR_F_GRAD_DEF, 4, G(fill_grad_def));
+ A(SPAN_ATTR_F_GRAD_RADIAL, 4, G(fill_grad_radial));
+ A(SPAN_ATTR_S_GRAD_ABC_Y, 4, G(stroke_grad_abc_y));
+ A(SPAN_ATTR_S_GRAD_DEF, 4, G(stroke_grad_def));
+ A(SPAN_ATTR_S_GRAD_RADIAL, 4, G(stroke_grad_radial));
+#undef G
+ A(SPAN_ATTR_MASK_OFF_SIZE, 4, offsetof(Span_Vertex_Gradient_Mask, mask_off_size));
+ A(SPAN_ATTR_MASK_COMP_INV, 2, offsetof(Span_Vertex_Gradient_Mask, mask_comp_inv));
+ break;
+ default: break;
+ }
+#undef COMMON
+#undef A
+ return n;
+}
+
+/* Apply @p n attribute descriptors against the currently bound VBO. */
+static void
+_span_attr_apply(const Span_Attr_Desc *d, int n, GLsizei stride)
+{
+ int i;
+ for (i = 0; i < n; i++)
+ {
+ glEnableVertexAttribArray((GLuint)d[i].loc);
+ glVertexAttribPointer((GLuint)d[i].loc, d[i].cnt, GL_FLOAT, GL_FALSE,
+ stride, (const void *)(uintptr_t)d[i].off);
+ }
+}
+
+/* Return the VAO for @p variant, creating it on first use. 0 means the
+ * caller must fall back to per-draw attribute setup. */
+static GLuint
+_span_vao_get(Span_Variant variant)
+{
+ Span_Attr_Desc desc[16];
+ int n;
+
+ _span_vao_probe();
+ if (!_gl_bind_vao) return 0;
+ if (_span_vao[variant]) return _span_vao[variant];
+
+ _gl_gen_vao(1, &_span_vao[variant]);
+ if (!_span_vao[variant]) return 0;
+
+ _gl_bind_vao(_span_vao[variant]);
+ glBindBuffer(GL_ARRAY_BUFFER, _span_vbo);
+ n = _span_attr_layout(variant, desc);
+ _span_attr_apply(desc, n, (GLsizei)span_vertex_size(variant));
+ _gl_bind_vao(0);
+ glBindBuffer(GL_ARRAY_BUFFER, 0);
+
+ return _span_vao[variant];
+}
+
/* ------------------------------------------------------------------ */
/* Shader compilation helpers */
/* ------------------------------------------------------------------ */
@@ -811,6 +973,31 @@ _link_program(Span_Shader *ss,
ss->program = glCreateProgram();
glAttachShader(ss->program, vs);
glAttachShader(ss->program, fs);
+
+ /* Bind attribute locations explicitly, before linking, so that every
+ * variant places a given semantic at the same index. Without this the
+ * linker is free to assign per-program locations, and a vertex array
+ * object — whose state is keyed by location, not by program — could not
+ * be shared between the programs that use the same vertex layout.
+ *
+ * Solid and gradient variants deliberately overlap on 5/6: a VAO is
+ * per-variant, and no variant declares both sets. */
+ glBindAttribLocation(ss->program, SPAN_ATTR_POSITION, "a_position");
+ glBindAttribLocation(ss->program, SPAN_ATTR_FBO_FILL_OFF, "a_fbo_fill_off");
+ glBindAttribLocation(ss->program, SPAN_ATTR_STROKE_OFF_FLAGS, "a_stroke_off_flags");
+ glBindAttribLocation(ss->program, SPAN_ATTR_X_MIN, "a_x_min");
+ glBindAttribLocation(ss->program, SPAN_ATTR_MUL_COL, "a_mul_col");
+ glBindAttribLocation(ss->program, SPAN_ATTR_FILL_COL, "a_fill_col");
+ glBindAttribLocation(ss->program, SPAN_ATTR_STROKE_COL, "a_stroke_col");
+ glBindAttribLocation(ss->program, SPAN_ATTR_F_GRAD_ABC_Y, "a_fill_grad_abc_y");
+ glBindAttribLocation(ss->program, SPAN_ATTR_F_GRAD_DEF, "a_fill_grad_def");
+ glBindAttribLocation(ss->program, SPAN_ATTR_F_GRAD_RADIAL, "a_fill_grad_radial");
+ glBindAttribLocation(ss->program, SPAN_ATTR_S_GRAD_ABC_Y, "a_stroke_grad_abc_y");
+ glBindAttribLocation(ss->program, SPAN_ATTR_S_GRAD_DEF, "a_stroke_grad_def");
+ glBindAttribLocation(ss->program, SPAN_ATTR_S_GRAD_RADIAL, "a_stroke_grad_radial");
+ glBindAttribLocation(ss->program, SPAN_ATTR_MASK_OFF_SIZE, "a_mask_off_size");
+ glBindAttribLocation(ss->program, SPAN_ATTR_MASK_COMP_INV, "a_mask_comp_inv");
+
glLinkProgram(ss->program);
glGetProgramiv(ss->program, GL_LINK_STATUS, &ok);
@@ -840,23 +1027,10 @@ _link_program(Span_Shader *ss,
/* Mask sampler — location -1 in non-mask shaders (safe no-op). */
ss->loc_mask_tex = glGetUniformLocation(ss->program, "u_mask_tex");
- /* Attribute locations — determined after link.
- * -1 returned for attributes not in this variant. */
- ss->attr_position = glGetAttribLocation(ss->program, "a_position");
- ss->attr_fbo_fill_off = glGetAttribLocation(ss->program, "a_fbo_fill_off");
- ss->attr_stroke_off_flags = glGetAttribLocation(ss->program, "a_stroke_off_flags");
- ss->attr_x_min = glGetAttribLocation(ss->program, "a_x_min");
- ss->attr_mul_col = glGetAttribLocation(ss->program, "a_mul_col");
- ss->attr_fill_col = glGetAttribLocation(ss->program, "a_fill_col");
- ss->attr_stroke_col = glGetAttribLocation(ss->program, "a_stroke_col");
- ss->attr_fill_grad_abc_y = glGetAttribLocation(ss->program, "a_fill_grad_abc_y");
- ss->attr_fill_grad_def = glGetAttribLocation(ss->program, "a_fill_grad_def");
- ss->attr_fill_grad_radial = glGetAttribLocation(ss->program, "a_fill_grad_radial");
- ss->attr_stroke_grad_abc_y = glGetAttribLocation(ss->program, "a_stroke_grad_abc_y");
- ss->attr_stroke_grad_def = glGetAttribLocation(ss->program, "a_stroke_grad_def");
- ss->attr_stroke_grad_radial= glGetAttribLocation(ss->program, "a_stroke_grad_radial");
- ss->attr_mask_off_size = glGetAttribLocation(ss->program, "a_mask_off_size");
- ss->attr_mask_comp_inv = glGetAttribLocation(ss->program, "a_mask_comp_inv");
+ /* Attribute locations are the constants bound above, not queried: an
+ * attribute the linker optimised out would report -1, but enabling an
+ * unused array is harmless and keeping the index fixed is what lets the
+ * per-variant VAO be shared across programs. */
return EINA_TRUE;
}
@@ -999,6 +1173,7 @@ span_shader_shutdown(void)
glDeleteProgram(ss->program);
ss->program = 0;
}
+ ss->samplers_bound = EINA_FALSE;
}
if (_white_mask_tex)
@@ -1007,6 +1182,19 @@ span_shader_shutdown(void)
_white_mask_tex = 0;
}
+ if (_gl_del_vao)
+ {
+ int v;
+ for (v = 0; v < SPAN_VARIANT_COUNT; v++)
+ if (_span_vao[v]) { _gl_del_vao(1, &_span_vao[v]); _span_vao[v] = 0; }
+ }
+ if (_span_vbo)
+ {
+ glDeleteBuffers(1, &_span_vbo);
+ _span_vbo = 0;
+ }
+
+
free(_span_pack_buf);
_span_pack_buf = NULL;
_span_pack_sz = 0;
@@ -1055,7 +1243,7 @@ span_collector_upload_textures(Span_Collector *sc, void *gc_ptr)
if (tex->evas_tex)
{
Evas_GL_Texture *existing = (Evas_GL_Texture *)tex->evas_tex;
- if (existing->h < (unsigned int)sc->height)
+ if (existing->h < sc->height)
{
evas_gl_common_texture_free(existing, EINA_TRUE);
tex->evas_tex = NULL;
@@ -1261,6 +1449,7 @@ span_shader_pipe_flush(Evas_Engine_GL_Context *gc, int pipe_idx)
GLuint mask_tex = gc->pipe[pipe_idx].shader.span_mask_tex;
float inv_tw = gc->pipe[pipe_idx].shader.span_inv_tw;
float inv_th = gc->pipe[pipe_idx].shader.span_inv_th;
+ GLuint vao;
/* Determine kind (0=solid, 1=gradient) and bind set from variant + textures. */
int kind = (variant == SPAN_VARIANT_GRADIENT ||
@@ -1285,112 +1474,59 @@ span_shader_pipe_flush(Evas_Engine_GL_Context *gc, int pipe_idx)
glUseProgram(ss->program);
- /* Uniforms — sampler bindings + pool reciprocals. */
+ /* Sampler uniforms are program state and never change: unit 0 is always
+ * the fill span texture, 1 the stroke, 2 the gradient ramp atlas, 3 the
+ * composite mask. Assign them once per program rather than on every
+ * draw. */
+ if (!ss->samplers_bound)
+ {
+ if (ss->loc_fill_spans >= 0) glUniform1i(ss->loc_fill_spans, 0);
+ if (ss->loc_stroke_spans >= 0) glUniform1i(ss->loc_stroke_spans, 1);
+ if (ss->loc_grad_ramp_atlas >= 0) glUniform1i(ss->loc_grad_ramp_atlas, 2);
+ if (ss->loc_mask_tex >= 0) glUniform1i(ss->loc_mask_tex, 3);
+ ss->samplers_bound = EINA_TRUE;
+ }
+
glActiveTexture(GL_TEXTURE0);
glBindTexture(GL_TEXTURE_2D, fill_tex ? fill_tex : stroke_tex);
- glUniform1i(ss->loc_fill_spans, 0);
-
glActiveTexture(GL_TEXTURE1);
glBindTexture(GL_TEXTURE_2D, stroke_tex ? stroke_tex : fill_tex);
- glUniform1i(ss->loc_stroke_spans, 1);
-
- if (ss->loc_inv_tw >= 0) glUniform1f(ss->loc_inv_tw, inv_tw);
- if (ss->loc_inv_th >= 0) glUniform1f(ss->loc_inv_th, inv_th);
-
if (ss->loc_grad_ramp_atlas >= 0 && atlas_tex)
{
glActiveTexture(GL_TEXTURE2);
glBindTexture(GL_TEXTURE_2D, atlas_tex);
- glUniform1i(ss->loc_grad_ramp_atlas, 2);
}
-
if (ss->loc_mask_tex >= 0 && mask_tex)
{
glActiveTexture(GL_TEXTURE3);
glBindTexture(GL_TEXTURE_2D, mask_tex);
- glUniform1i(ss->loc_mask_tex, 3);
}
- /* VBO upload: upload span vertex data once per flush as GL_STREAM_DRAW.
- * Using a VBO avoids the per-draw driver scratch-buffer allocation that
- * client-side vertex arrays require, recovering the performance lost by
- * the attribute-batching refactor on non-batching draws.
- *
- * Lazy allocation: glGenBuffers fires on first use; the context teardown
- * already calls glDeleteBuffers for array.buffer (unconditionally for span
- * pipes via the span_vertex_data cleanup block). */
- if (!gc->pipe[pipe_idx].array.buffer)
- glGenBuffers(1, &gc->pipe[pipe_idx].array.buffer);
- glBindBuffer(GL_ARRAY_BUFFER, gc->pipe[pipe_idx].array.buffer);
+ if (ss->loc_inv_tw >= 0) glUniform1f(ss->loc_inv_tw, inv_tw);
+ if (ss->loc_inv_th >= 0) glUniform1f(ss->loc_inv_th, inv_th);
+
+ /* One streaming VBO shared by every span pipe. Re-specifying it with
+ * glBufferData orphans the previous storage, so the driver never has to
+ * stall on data the GPU may still be reading. */
+ if (!_span_vbo) glGenBuffers(1, &_span_vbo);
+ vao = _span_vao_get(variant);
+ if (vao) _gl_bind_vao(vao);
+ glBindBuffer(GL_ARRAY_BUFFER, _span_vbo);
+
glBufferData(GL_ARRAY_BUFFER,
(GLsizeiptr)gc->pipe[pipe_idx].array.span_vertex_data_used,
vdata,
GL_STREAM_DRAW);
- /* Attribute pointer setup.
- * BIND_ATTR(loc, components, byte_offset_into_vbo) enables and binds each
- * attribute using VBO byte offsets (not CPU pointers). The VBO is bound
- * above; GL interprets the last argument as an offset when a buffer is
- * bound to GL_ARRAY_BUFFER. Locations left enabled after draw are benign —
- * image/font shaders bind their own slots (SHAD_VERTEX/SHAD_COLOR)
- * explicitly before drawing, and never fetch from span-specific locations. */
-#define BIND_ATTR(loc, cnt, off) \
- do { \
- if ((loc) >= 0) { \
- glEnableVertexAttribArray((GLuint)(loc)); \
- glVertexAttribPointer((GLuint)(loc), (cnt), GL_FLOAT, GL_FALSE, \
- stride, (const void *)(uintptr_t)(off)); \
- } \
- } while (0)
-
- /* Common fields — all variants share Span_Vertex_Common at offset 0. */
- BIND_ATTR(ss->attr_position, 2, offsetof(Span_Vertex_Solid, c) + offsetof(Span_Vertex_Common, pos));
- BIND_ATTR(ss->attr_fbo_fill_off, 4, offsetof(Span_Vertex_Solid, c) + offsetof(Span_Vertex_Common, fbo_fill_off));
- BIND_ATTR(ss->attr_stroke_off_flags, 4, offsetof(Span_Vertex_Solid, c) + offsetof(Span_Vertex_Common, stroke_off_flags));
- BIND_ATTR(ss->attr_x_min, 2, offsetof(Span_Vertex_Solid, c) + offsetof(Span_Vertex_Common, x_min));
- BIND_ATTR(ss->attr_mul_col, 4, offsetof(Span_Vertex_Solid, c) + offsetof(Span_Vertex_Common, mul_col));
-
- switch (variant)
+ if (!vao)
{
- case SPAN_VARIANT_SOLID:
- BIND_ATTR(ss->attr_fill_col, 4, offsetof(Span_Vertex_Solid, fill_col));
- BIND_ATTR(ss->attr_stroke_col, 4, offsetof(Span_Vertex_Solid, stroke_col));
- break;
- case SPAN_VARIANT_SOLID_MASK:
- BIND_ATTR(ss->attr_fill_col, 4,
- offsetof(Span_Vertex_Solid_Mask, s) + offsetof(Span_Vertex_Solid, fill_col));
- BIND_ATTR(ss->attr_stroke_col, 4,
- offsetof(Span_Vertex_Solid_Mask, s) + offsetof(Span_Vertex_Solid, stroke_col));
- BIND_ATTR(ss->attr_mask_off_size, 4, offsetof(Span_Vertex_Solid_Mask, mask_off_size));
- BIND_ATTR(ss->attr_mask_comp_inv, 2, offsetof(Span_Vertex_Solid_Mask, mask_comp_inv));
- break;
- case SPAN_VARIANT_GRADIENT:
- BIND_ATTR(ss->attr_fill_grad_abc_y, 4, offsetof(Span_Vertex_Gradient, fill_grad_abc_y));
- BIND_ATTR(ss->attr_fill_grad_def, 4, offsetof(Span_Vertex_Gradient, fill_grad_def));
- BIND_ATTR(ss->attr_fill_grad_radial, 4, offsetof(Span_Vertex_Gradient, fill_grad_radial));
- BIND_ATTR(ss->attr_stroke_grad_abc_y, 4, offsetof(Span_Vertex_Gradient, stroke_grad_abc_y));
- BIND_ATTR(ss->attr_stroke_grad_def, 4, offsetof(Span_Vertex_Gradient, stroke_grad_def));
- BIND_ATTR(ss->attr_stroke_grad_radial, 4, offsetof(Span_Vertex_Gradient, stroke_grad_radial));
- break;
- case SPAN_VARIANT_GRADIENT_MASK:
- BIND_ATTR(ss->attr_fill_grad_abc_y, 4,
- offsetof(Span_Vertex_Gradient_Mask, g) + offsetof(Span_Vertex_Gradient, fill_grad_abc_y));
- BIND_ATTR(ss->attr_fill_grad_def, 4,
- offsetof(Span_Vertex_Gradient_Mask, g) + offsetof(Span_Vertex_Gradient, fill_grad_def));
- BIND_ATTR(ss->attr_fill_grad_radial, 4,
- offsetof(Span_Vertex_Gradient_Mask, g) + offsetof(Span_Vertex_Gradient, fill_grad_radial));
- BIND_ATTR(ss->attr_stroke_grad_abc_y, 4,
- offsetof(Span_Vertex_Gradient_Mask, g) + offsetof(Span_Vertex_Gradient, stroke_grad_abc_y));
- BIND_ATTR(ss->attr_stroke_grad_def, 4,
- offsetof(Span_Vertex_Gradient_Mask, g) + offsetof(Span_Vertex_Gradient, stroke_grad_def));
- BIND_ATTR(ss->attr_stroke_grad_radial, 4,
- offsetof(Span_Vertex_Gradient_Mask, g) + offsetof(Span_Vertex_Gradient, stroke_grad_radial));
- BIND_ATTR(ss->attr_mask_off_size, 4, offsetof(Span_Vertex_Gradient_Mask, mask_off_size));
- BIND_ATTR(ss->attr_mask_comp_inv, 2, offsetof(Span_Vertex_Gradient_Mask, mask_comp_inv));
- break;
- default: break;
+ /* No vertex array objects: respecify the layout for every draw.
+ * Locations left enabled afterwards are benign — image/font shaders
+ * bind their own slots explicitly and never fetch from ours. */
+ Span_Attr_Desc desc[16];
+ int n = _span_attr_layout(variant, desc);
+ _span_attr_apply(desc, n, stride);
}
-#undef BIND_ATTR
glEnable(GL_BLEND);
glBlendFunc(GL_ONE, GL_ONE_MINUS_SRC_ALPHA);
@@ -1398,11 +1534,11 @@ span_shader_pipe_flush(Evas_Engine_GL_Context *gc, int pipe_idx)
glDrawArrays(GL_TRIANGLES, 0, nverts);
- /* Unbind span VBO so subsequent client-side array draws (image/font)
- * are not accidentally interpreted as VBO-offset draws. */
+ /* Leave the default vertex array and no array buffer bound so that the
+ * client-side array draws of the image/font pipes are unaffected, and
+ * hand back texture unit 0 which those paths bind without selecting. */
+ if (vao) _gl_bind_vao(0);
glBindBuffer(GL_ARRAY_BUFFER, 0);
-
- /* Restore active texture unit. */
glActiveTexture(GL_TEXTURE0);
/* Invalidate Evas GL state cache fields touched by the span shader. */
--
To stop receiving notification emails like this one, please contact
the administrator of this repository.