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 c0f9cb62b8f789ec52200e03a00596d0202510d1
Author: [email protected] <[email protected]>
AuthorDate: Mon Aug 10 20:48:59 2026 -0600
fix(evas_ector_gl): fall back to CPU rasterization when spans are unusable
eng_ector_begin installed the span collector callbacks unconditionally, so
on a device where the span shaders cannot run, ector emitted spans that
nothing consumed while eng_ector_end cleared the FBO sub-rect regardless.
The visible result was vector graphics rendering fully transparent, with no
fallback of any kind.
Leave the callbacks NULL when span_path_usable() says no. The software
rasterizer then writes into the CPU buffer eng_ector_begin already
allocates, and eng_ector_end uploads it - the path the engine used before
the span renderer existed.
That upload path had actually been deleted by commit a18c0d92a4 ("refactor:
remove software fallback from GL engine"), which made the FBO-backed span
surface the only surface type eng_ector_surface_create ever produced.
Leaving the collectors NULL alone was not enough: the software rasterizer's
default pixel buffer lives inside the ector object, disconnected from that
FBO texture, so nothing ever reached the screen (verified: 0 non-white
pixels with EVAS_GL_SPAN_TIER=off before this fix). Restored the
CPU-backed surface/begin/end paths that a18c0d92a4 removed, now gated on
span_path_usable() instead of the old use_span_buffer/use_gl flags, in
eng_ector_surface_create, eng_ector_mask_surface_create, eng_ector_begin
and eng_ector_end.
Verified with EVAS_GL_SPAN_TIER=off: vector tests 117 through 122 render and
match the software backend within tolerance, and test 119 matches the
span-enabled render pixel-for-pixel on non-white pixel count (67694). Also
verified a genuine shader-compile failure (not just the tier override)
reaches the same fallback.
Co-Authored-By: Claude Opus 5 (1M context) <[email protected]>
---
src/modules/evas/engines/gl_generic/evas_engine.c | 113 ++++++++++++++++++----
1 file changed, 92 insertions(+), 21 deletions(-)
diff --git a/src/modules/evas/engines/gl_generic/evas_engine.c b/src/modules/evas/engines/gl_generic/evas_engine.c
index bf85e6c68e..63a022c471 100644
--- a/src/modules/evas/engines/gl_generic/evas_engine.c
+++ b/src/modules/evas/engines/gl_generic/evas_engine.c
@@ -2601,17 +2601,31 @@ eng_ector_surface_create(void *engine, int width, int height, int *error)
*error = EINA_FALSE;
- /* FBO-backed render surface. The span shader draws directly into the
- * texture attached to this image's FBO. Use the atlas pool so multiple
- * VG objects share a single GL FBO texture, eliminating per-frame
- * glGenFramebuffers/glDeleteFramebuffers overhead when VG object sizes
- * change. The image's tex->x/y carry the atlas sub-region offset. */
- {
- Evas_Engine_GL_Context *ctx = gl_generic_context_find(engine, EINA_TRUE);
- surface = evas_gl_common_image_surface_noscale_new(ctx, width, height, EINA_TRUE);
- if (!surface)
- *error = EINA_TRUE;
- }
+ if (span_path_usable())
+ {
+ /* FBO-backed render surface. The span shader draws directly into the
+ * texture attached to this image's FBO. Use the atlas pool so multiple
+ * VG objects share a single GL FBO texture, eliminating per-frame
+ * glGenFramebuffers/glDeleteFramebuffers overhead when VG object sizes
+ * change. The image's tex->x/y carry the atlas sub-region offset. */
+ Evas_Engine_GL_Context *ctx = gl_generic_context_find(engine, EINA_TRUE);
+ surface = evas_gl_common_image_surface_noscale_new(ctx, width, height, EINA_TRUE);
+ if (!surface)
+ *error = EINA_TRUE;
+ }
+ else
+ {
+ /* Span path unusable: fall back to a plain CPU-backed image. The
+ * software rasterizer draws into it directly (no span collectors
+ * installed, see eng_ector_begin) and eng_ector_end uploads the
+ * result to the GPU with eng_image_data_put. */
+ surface = eng_image_new_from_copied_data(engine, width, height, NULL,
+ EINA_TRUE, EVAS_COLORSPACE_ARGB8888);
+ if (!surface)
+ *error = EINA_TRUE;
+ else /* Hint for zero-copy texture upload. */
+ eng_image_content_hint_set(engine, surface, EVAS_IMAGE_CONTENT_HINT_DYNAMIC);
+ }
return surface;
}
@@ -2621,16 +2635,22 @@ eng_ector_mask_surface_create(void *engine, int width, int height, int *error)
{
*error = EINA_FALSE;
- /* Mask FBO must use a dedicated texture — not shared with the atlas.
- * The main VG FBO and the mask FBO would otherwise map to the same GL
- * texture object, creating a read/write feedback loop when the span
- * shader samples the mask while rendering into the main FBO. */
- {
- Evas_Engine_GL_Context *ctx = gl_generic_context_find(engine, EINA_TRUE);
- void *surface = evas_gl_common_image_surface_noscale_noatlas_new(ctx, width, height, EINA_TRUE);
- if (!surface) *error = EINA_TRUE;
- return surface;
- }
+ if (span_path_usable())
+ {
+ /* Mask FBO must use a dedicated texture — not shared with the atlas.
+ * The main VG FBO and the mask FBO would otherwise map to the same GL
+ * texture object, creating a read/write feedback loop when the span
+ * shader samples the mask while rendering into the main FBO. */
+ Evas_Engine_GL_Context *ctx = gl_generic_context_find(engine, EINA_TRUE);
+ void *surface = evas_gl_common_image_surface_noscale_noatlas_new(ctx, width, height, EINA_TRUE);
+ if (!surface) *error = EINA_TRUE;
+ return surface;
+ }
+ else
+ {
+ /* CPU fallback: no atlas concern, delegate to the regular path. */
+ return eng_ector_surface_create(engine, width, height, error);
+ }
}
static void
@@ -2744,6 +2764,30 @@ eng_ector_begin(void *engine, void *surface,
void *context EINA_UNUSED, Ector_Surface *ector,
int x, int y, Eina_Bool do_async EINA_UNUSED)
{
+ if (!span_path_usable())
+ {
+ /* CPU fallback: surface is a plain image (see eng_ector_surface_create).
+ * Draw directly into the image's own pixel buffer with the software
+ * rasterizer, exactly as the engine did before the span path existed.
+ * No span collectors are involved, so ector must write into pixels
+ * that eng_ector_end can hand straight back to the GPU. */
+ Evas_GL_Image *glim = surface;
+ DATA32 *pixels;
+ int w, h, stride, load_err;
+
+ glim = eng_image_data_get(engine, glim, EINA_TRUE, &pixels, &load_err, NULL);
+ if (!glim || !pixels) return EINA_FALSE;
+ eng_image_stride_get(engine, glim, &stride);
+ eng_image_size_get(engine, glim, &w, &h);
+
+ memset(pixels, 0, stride * h);
+
+ ector_buffer_pixels_set(ector, pixels, w, h, stride,
+ EFL_GFX_COLORSPACE_ARGB8888, EINA_TRUE);
+ ector_surface_reference_point_set(ector, x, y);
+ return EINA_TRUE;
+ }
+
{
Evas_GL_Image *glim = surface;
int w, h;
@@ -2809,6 +2853,12 @@ eng_ector_begin(void *engine, void *surface,
Span_Data *sd = &pd->rasterizer->fill_data;
sd->span_collector = NULL;
sd->span_is_stroke = EINA_FALSE;
+
+ /* Reaching here means span_path_usable() already returned
+ * EINA_TRUE (checked at function entry above) — the
+ * !span_path_usable() case returns early with the CPU
+ * fallback and never installs collectors, so ector does not
+ * emit spans that nothing consumes. */
sd->collector_solid = _collect_spans_solid;
sd->collector_gradient = _collect_spans_gradient;
sd->collector_composite = _collect_spans_composite;
@@ -3064,6 +3114,27 @@ eng_ector_end(void *engine,
Ector_Surface *ector,
Eina_Bool do_async EINA_UNUSED)
{
+ if (!span_path_usable())
+ {
+ /* CPU fallback: the software rasterizer wrote directly into the
+ * image's own pixel buffer (set up in eng_ector_begin). Push it
+ * to the GPU the same way the engine did before the span path
+ * existed. The double eng_image_data_put() call mirrors that
+ * pre-span code: the first marks the image dirty, the second
+ * uploads it. */
+ Evas_GL_Image *glim = surface;
+ DATA32 *pixels;
+ int load_err;
+
+ glim = eng_image_data_get(engine, glim, EINA_FALSE, &pixels, &load_err, NULL);
+
+ eng_image_data_put(engine, glim, pixels);
+ eng_image_data_put(engine, glim, pixels);
+ ector_buffer_pixels_set(ector, NULL, 0, 0, 0, EFL_GFX_COLORSPACE_ARGB8888, EINA_TRUE);
+ evas_common_cpu_end_opt();
+ return;
+ }
+
{
Ector_Software_Surface_Data *espd = efl_data_scope_get(ector, ECTOR_SOFTWARE_SURFACE_CLASS);
Evas_GL_Image *glim = surface;
--
To stop receiving notification emails like this one, please contact
the administrator of this repository.