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.

Reply via email to