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 b4f54e9f9c527f87670a55301941cca89fa21189
Author: [email protected] <[email protected]>
AuthorDate: Wed May 6 09:13:26 2026 -0600
perf(evas_ector_gl): use VBO for span vertex data to recover non-batch FPS
The 5-commit attribute-batching refactor (4a9c602073..b57ed35aaa) unified gradient
ramp routing and collapsed the merge predicate to 6 fields (down from 44), delivering
21% FPS gains on tests where many similar shapes batch together (e.g., VG Basic Network).
However, most Expedite VG tests regressed 5-26% vs the pre-refactor baseline 5a67a378ce.
Root cause: the refactor switched from register-write uniforms to client-side vertex
arrays (glVertexAttribPointer with CPU pointers). Client-side arrays force the GL driver
to allocate transient scratch buffers + memcpy 100-1100 bytes per draw + track CPU buffer
lifetime for GPU lifetime safety. On tests with few shapes (no batching benefit), this
per-draw overhead outweighs the merge savings.
Fix: migrate to a per-pipe VBO following the canonical pattern at lines 4663-4711
(image/font flush). Each pipe's array.buffer field is lazily allocated (glGenBuffers on
first flush) and refilled per flush via glBufferData(GL_STREAM_DRAW, used, vdata), letting
the driver orphan-and-respecify for safe GPU pipelining. glVertexAttribPointer now uses
VBO byte offsets instead of CPU+base pointers, eliminating the per-draw scratch-buffer
allocation. glBindBuffer(GL_ARRAY_BUFFER, 0) after glDrawArrays restores no-VBO state
for image/font draws (which still use client-side arrays).
VBO lifetime: the existing cleanup loop (evas_gl_common_context_free, lines 1442-1446)
deletes every pipe's array.buffer when glsym_glMapBuffer is present; for the no-MapBuffer
case (uncommon, some embedded GL drivers), a fallback delete is added in the span_vertex_data
cleanup block.
Verification: Expedite VG benchmark now byte-identical (10/10 PNGs) vs both the pre-refactor
baseline (5a67a378ce) and the just-landed fix commit (c3a5bede1e). Tests with no batching
(VG Basic Rect, Composite, Gradient) recover expected FPS; VG Basic Network preserves its
21% gain. ector suite 19/0/0; evas suite 120/0/0 (unchanged).
Co-Authored-By: Claude Opus 4.7 (1M context) <[email protected]>
---
.../evas/engines/gl_common/evas_gl_context.c | 17 ++++++++++--
.../engines/gl_generic/evas_ector_gl_span_shader.c | 32 ++++++++++++++++++----
2 files changed, 42 insertions(+), 7 deletions(-)
diff --git a/src/modules/evas/engines/gl_common/evas_gl_context.c b/src/modules/evas/engines/gl_common/evas_gl_context.c
index bf27e17ce0..cc4b8858b4 100644
--- a/src/modules/evas/engines/gl_common/evas_gl_context.c
+++ b/src/modules/evas/engines/gl_common/evas_gl_context.c
@@ -1466,6 +1466,17 @@ evas_gl_common_context_free(Evas_Engine_GL_Context *gc)
gc->pipe[i].array.span_vertex_data_size = 0;
gc->pipe[i].array.span_vertex_data_used = 0;
}
+ /* Delete the span VBO when the MapBuffer-gated loop above did
+ * not run. When glsym_glMapBuffer is present, the loop at
+ * lines ~1442-1446 already deletes every pipe's array.buffer
+ * (image/font and span share that field), so this block is
+ * a no-op there. When MapBuffer is absent the loop is
+ * skipped, and the span path (which uses plain glBufferData
+ * rather than glMapBuffer) needs this fallback to avoid
+ * leaking the VBO. */
+ if (gc->pipe[i].array.buffer &&
+ !(glsym_glMapBuffer && glsym_glUnmapBuffer))
+ glDeleteBuffers(1, &gc->pipe[i].array.buffer);
}
}
@@ -4724,8 +4735,10 @@ shader_array_flush(Evas_Engine_GL_Context *gc)
masksam_ptr = (unsigned char *)gc->pipe[i].array.masksam;
}
- // use_vertex is always true here (span pipes take the early-return
- // path above; non-span pipes always populate array.vertex via PIPE_GROW).
+ // use_vertex is always true here. Span pipes take the early-return path
+ // above (they use span_vertex_data + a VBO via span_shader_pipe_flush,
+ // not array.vertex). Non-span pipes always populate array.vertex via
+ // PIPE_GROW.
glVertexAttribPointer(SHAD_VERTEX, VERTEX_CNT, GL_FLOAT, GL_FALSE, 0, vertex_ptr);
if (gc->pipe[i].array.use_color)
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 c1439d746e..26cc3fd823 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
@@ -1103,17 +1103,35 @@ span_shader_pipe_flush(Evas_Engine_GL_Context *gc, int pipe_idx)
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);
+ 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_in_struct) enables and binds each
- * attribute. 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. */
+ * 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 char *)vdata + (off)); \
+ stride, (const void *)(uintptr_t)(off)); \
} \
} while (0)
@@ -1172,6 +1190,10 @@ 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. */
+ glBindBuffer(GL_ARRAY_BUFFER, 0);
+
/* Restore active texture unit. */
glActiveTexture(GL_TEXTURE0);
--
To stop receiving notification emails like this one, please contact
the administrator of this repository.