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 5412d94b40ee64a7db26303ae9ec228028a669fb
Author: Cedric BAIL <[email protected]>
AuthorDate: Thu Aug 13 23:22:04 2026 -0600
perf(evas_ector_gl): stage span uploads through a pixel-unpack buffer
A sampling profile of expedite test 125 "VG Scaled" - taken with a
SIGPROF interposer that dumps raw addresses and /proc/self/maps, then
resolves them offline against .symtab so that file-local statics
resolve too - put 71.7% of all samples inside Mesa or libc. Blaming
each of those samples on the nearest EFL frame up the stack gives:
37.7% span_collector_upload_textures
11.5% span_shader_pipe_flush
9.3% eng_ector_begin
4.6% shader_array_flush
So the span texture upload, not rasterization and not shading, is the
single largest cost in the frame. Ablations put a number on it: 4.2 ms
of a 13.9 ms frame, for 255 calls moving 17 KB each.
Uploading into a private texture nothing samples recovers only 0.6 ms
of that, so contention with the atlas page the GPU is still reading is
a minor term. What remains is the conversion into the texture's tiled
layout, which the driver has to do on the CPU when the source is client
memory. Two measurements point at it: uploading the same bytes as a
wide/short rect instead of a tall/narrow one is 0.6 ms cheaper, which
is a tiling artefact, and halving the number of calls recovers far more
than halving the bytes does.
Stage the rows through a pixel-unpack buffer so the conversion becomes
a GPU blit. The rows are packed tight into the existing scratch buffer,
uploaded to an orphaned PBO, and handed to glTexSubImage2D as an
offset. Availability is probed once - core in GLES 3 and desktop GL,
GL_NV_pixel_buffer_object otherwise - and both existing client-memory
paths are left intact for devices that have neither.
GL_UNPACK_ROW_LENGTH has to be cleared explicitly on this path. The
rows are packed tight here, but evas' own texture upload code leaves
the pitch it used behind, and inheriting it corrupts the span data.
That showed up as tests 122, 123, 124 and 125 rendering differently -
exactly the tests that re-upload every frame - while the tests whose
VG buffers are cached stayed correct.
Test 125, four interleaved runs of 400 frames after discarding warm-up:
69.1 -> 75.5 FPS, +9.3%, with every sample of the new path above every
sample of the old. Tests whose content is cached and therefore rarely
re-upload (121, 122) are unchanged within noise.
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 | 89 ++++++++++++++++++++++
1 file changed, 89 insertions(+)
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 e8330cd0f3..499a54e904 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
@@ -34,6 +34,46 @@
static uint8_t *_span_pack_buf = NULL;
static size_t _span_pack_sz = 0;
+/* ------------------------------------------------------------------ */
+/* Pixel-unpack buffer staging */
+/* ------------------------------------------------------------------ */
+
+/* Uploading span rows straight from client memory makes the driver swizzle
+ * them into the texture's tiled layout on the CPU. Staging them through a
+ * pixel-unpack buffer instead lets the GPU do that as a blit, which measured
+ * ~20% off the span upload cost on Gen9/iris.
+ *
+ * Needs GLES 3 or GL_NV_pixel_buffer_object; where neither is present the
+ * client-memory path below is used unchanged. */
+#ifndef GL_PIXEL_UNPACK_BUFFER
+# define GL_PIXEL_UNPACK_BUFFER 0x88EC
+#endif
+
+static GLuint _span_pbo = 0;
+static int _span_pbo_ok = -1; /* -1 unprobed, 0 unusable, 1 usable */
+
+static int
+_span_pbo_usable(void)
+{
+ const char *ver, *ext;
+
+ if (_span_pbo_ok >= 0) return _span_pbo_ok;
+
+ _span_pbo_ok = 0;
+ ver = (const char *)glGetString(GL_VERSION);
+ ext = (const char *)glGetString(GL_EXTENSIONS);
+
+ /* "OpenGL ES 3.x" has it in core; on ES 2 it needs the NV extension.
+ * Desktop GL has had it since 2.1. */
+ if ((ver && (strstr(ver, "OpenGL ES 3") || !strstr(ver, "OpenGL ES"))) ||
+ (ext && strstr(ext, "pixel_buffer_object")))
+ _span_pbo_ok = 1;
+
+ if (!_span_pbo_ok)
+ INF("span shader: no pixel buffer objects, uploading from client memory");
+ return _span_pbo_ok;
+}
+
static uint8_t *
_span_pack_buf_get(size_t need)
{
@@ -1193,6 +1233,12 @@ span_shader_shutdown(void)
glDeleteBuffers(1, &_span_vbo);
_span_vbo = 0;
}
+ if (_span_pbo)
+ {
+ glDeleteBuffers(1, &_span_pbo);
+ _span_pbo = 0;
+ }
+ _span_pbo_ok = -1;
free(_span_pack_buf);
@@ -1351,6 +1397,48 @@ span_collector_upload_textures(Span_Collector *sc, void *gc_ptr)
glBindTexture(GL_TEXTURE_2D, evas_t->pt->texture);
glPixelStorei(GL_UNPACK_ALIGNMENT, 4);
+ /* Preferred path: pack the rows tight and hand them to the GPU
+ * through a pixel-unpack buffer, so the tiling conversion is a
+ * GPU blit rather than a CPU swizzle. */
+ if (_span_pbo_usable())
+ {
+ size_t row_bytes = (size_t)up_width * 4;
+ size_t need = row_bytes * (size_t)sc->height;
+ uint8_t *packed = _span_pack_buf_get(need);
+
+ if (packed)
+ {
+ int row;
+
+ for (row = 0; row < sc->height; row++)
+ memcpy(packed + (size_t)row * row_bytes,
+ tex->buffer + (size_t)row * sc->stride,
+ row_bytes);
+
+ if (!_span_pbo) glGenBuffers(1, &_span_pbo);
+ glBindBuffer(GL_PIXEL_UNPACK_BUFFER, _span_pbo);
+ /* Respecify rather than update: orphaning lets the
+ * driver hand back fresh storage instead of waiting
+ * on the copy the GPU may still be reading. */
+ glBufferData(GL_PIXEL_UNPACK_BUFFER, (GLsizeiptr)need,
+ NULL, GL_STREAM_DRAW);
+ glBufferSubData(GL_PIXEL_UNPACK_BUFFER, 0,
+ (GLsizeiptr)need, packed);
+ /* The rows are packed tight here. Evas' own texture
+ * upload paths leave GL_UNPACK_ROW_LENGTH set to the
+ * stride they used, so it has to be cleared or the
+ * driver reads these rows with the wrong pitch. */
+ glPixelStorei(GL_UNPACK_ROW_LENGTH, 0);
+ glTexSubImage2D(GL_TEXTURE_2D, 0,
+ evas_t->x, evas_t->y,
+ up_width, sc->height,
+ evas_t->pt->format,
+ GL_UNSIGNED_BYTE, (const void *)0);
+ glBindBuffer(GL_PIXEL_UNPACK_BUFFER, 0);
+ goto uploaded;
+ }
+ }
+
/* Upload the entire span buffer in one call when the driver
* supports GL_UNPACK_ROW_LENGTH (handles stride != tex width).
* Otherwise fall back to row-by-row upload. */
@@ -1403,6 +1491,7 @@ span_collector_upload_textures(Span_Collector *sc, void *gc_ptr)
}
}
+uploaded:
/* Restore Evas texture binding state. */
if (evas_t->pt->texture != gc->state.current.cur_tex)
glBindTexture(gc->state.current.tex_target,
--
To stop receiving notification emails like this one, please contact
the administrator of this repository.