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 ed3deef8700541670b6a3752b519581a2334e113
Author: Cedric BAIL <[email protected]>
AuthorDate: Fri Aug 14 11:10:48 2026 -0600

    tests(evas): cover vector group opacity and mixed object sizes
    
    Two scenes, run on every engine that can be brought up. That last part
    is the point: vector shapes go through a span-buffer path on the GL
    engine that the software engine does not use, so bugs in it are
    invisible to a buffer-engine test, and all three of the bugs these
    cover were GL-only. Engines that cannot be created - no display, not
    built - are skipped rather than failed, so the buffer engine always
    runs and the rest join in where they can.
    
    The scene is a narrow vector object followed by a much wider one whose
    root is a container with alpha below 255. Both halves matter. Every
    vector object on a canvas shares one ector surface, so the narrow one
    fixes that surface's stride and the wide one then renders through it;
    the group-opacity path additionally swaps the surface's pixel buffer
    out and back while it renders. Between them that reached a buffer sized
    from the wrong width, a use-after-free when the swap freed a buffer the
    surface owned, and a group whose alpha never reached the framebuffer.
    
    The first test asserts nothing beyond surviving, which is most of what
    was wanted - it segfaulted before those fixes. The second samples the
    centre pixel and requires the group's alpha to have been applied, with
    a wide tolerance, since it is checking that the alpha arrived at all
    rather than the exact rounding of the blend.
    
    Worth knowing for anything added here later: meson sets
    MALLOC_PERTURB_, which is what turned the span page's teardown
    use-after-free from an occasional crash into a reliable one. Running
    these by hand without it can look clean when they are not.
    
    Co-Authored-By: Claude Opus 5 (1M context) <[email protected]>
---
 src/tests/evas/evas_suite.c   |   1 +
 src/tests/evas/evas_suite.h   |   1 +
 src/tests/evas/evas_test_vg.c | 186 ++++++++++++++++++++++++++++++++++++++++++
 src/tests/evas/meson.build    |   1 +
 4 files changed, 189 insertions(+)

diff --git a/src/tests/evas/evas_suite.c b/src/tests/evas/evas_suite.c
index 05faf08ff8..9ed7a22a33 100644
--- a/src/tests/evas/evas_suite.c
+++ b/src/tests/evas/evas_suite.c
@@ -27,6 +27,7 @@ static const Efl_Test_Case etc[] = {
   { "Efl Canvas Animation", efl_test_canvas_animation },
   { "Map", evas_test_map },
   { "Premul", evas_test_premul },
+  { "VG", evas_test_vg },
   { NULL, NULL }
 };
 
diff --git a/src/tests/evas/evas_suite.h b/src/tests/evas/evas_suite.h
index 76a10a19b5..0a5e66fd60 100644
--- a/src/tests/evas/evas_suite.h
+++ b/src/tests/evas/evas_suite.h
@@ -11,6 +11,7 @@ void evas_test_textblock(TCase *tc);
 void evas_test_text(TCase *tc);
 void evas_test_callbacks(TCase *tc);
 void evas_test_render_engines(TCase *tc);
+void evas_test_vg(TCase *tc);
 void evas_test_filters(TCase *tc);
 void evas_test_image_object(TCase *tc);
 void evas_test_image_object2(TCase *tc);
diff --git a/src/tests/evas/evas_test_vg.c b/src/tests/evas/evas_test_vg.c
new file mode 100644
index 0000000000..5cb11c9841
--- /dev/null
+++ b/src/tests/evas/evas_test_vg.c
@@ -0,0 +1,186 @@
+/* Vector object rendering tests.
+ *
+ * These run on every engine that can be brought up, because the interesting
+ * failures have been engine-specific: the GL engine rasterises vector shapes
+ * through a span-buffer path that the software engine does not use, so a bug
+ * in it is invisible to a buffer-engine test.  Engines that cannot be created
+ * here (no display, not built) are skipped rather than failed.
+ */
+
+#ifdef HAVE_CONFIG_H
+# include "config.h"
+#endif
+
+#define EFL_BETA_API_SUPPORT 1
+#define EFL_EO_API_SUPPORT 1
+
+#include <stdio.h>
+
+#include <Evas.h>
+#include <Ecore_Evas.h>
+
+#include "evas_suite.h"
+
+#define WIN_W 256
+#define WIN_H 256
+
+/* Engines to attempt.  "buffer" is always available and headless; the rest
+ * are tried and skipped when they cannot be created. */
+static const char *_engines[] = { "buffer", "opengl_x11", NULL };
+
+static Eo *
+_vg_object_add(Evas *e, int x, int y, int w, int h, int alpha)
+{
+   Eo *vg, *shape, *root;
+
+   vg = efl_add(EFL_CANVAS_VG_OBJECT_CLASS, e);
+   efl_gfx_entity_size_set(vg, EINA_SIZE2D(w, h));
+   efl_gfx_entity_position_set(vg, EINA_POSITION2D(x, y));
+   efl_gfx_entity_visible_set(vg, EINA_TRUE);
+
+   if (alpha < 255)
+     {
+        /* A container whose colour has alpha below 255 asks for group
+         * opacity, which is a different rendering path from a shape that is
+         * merely translucent. */
+        root = efl_add(EFL_CANVAS_VG_CONTAINER_CLASS, vg);
+        efl_gfx_color_set(root, alpha, alpha, alpha, alpha);
+        shape = efl_add(EFL_CANVAS_VG_SHAPE_CLASS, root);
+     }
+   else
+     {
+        shape = efl_add(EFL_CANVAS_VG_SHAPE_CLASS, vg);
+        root = shape;
+     }
+
+   /* Opaque white, so that whatever alpha survives to the framebuffer is
+    * readable straight off one channel. */
+   efl_gfx_path_append_rect(shape, 0, 0, w, h, 0, 0);
+   efl_gfx_color_set(shape, 255, 255, 255, 255);
+
+   efl_canvas_vg_object_root_node_set(vg, root);
+   return vg;
+}
+
+/* Render the scene @p build makes and return the pixel at (@p px, @p py),
+ * or EINA_FALSE when this engine is unavailable. */
+static Eina_Bool
+_scene_sample(const char *engine, void (*build)(Evas *e),
+              int frames, int px, int py, unsigned int *out)
+{
+   Ecore_Evas *ee;
+   Evas *e;
+   Evas_Object *snap, *bg;
+   unsigned int *pixels;
+   int i, sw = 0;
+
+   ee = ecore_evas_new(engine, 0, 0, WIN_W, WIN_H, NULL);
+   if (!ee)
+     {
+        printf("Skipping: cannot create ecore_evas for '%s'\n", engine);
+        return EINA_FALSE;
+     }
+   ecore_evas_show(ee);
+   ecore_evas_manual_render_set(ee, EINA_TRUE);
+   e = ecore_evas_get(ee);
+
+   /* Opaque black behind, so a sampled pixel reports coverage directly. */
+   bg = evas_object_rectangle_add(e);
+   evas_object_color_set(bg, 0, 0, 0, 255);
+   evas_object_geometry_set(bg, 0, 0, WIN_W, WIN_H);
+   evas_object_show(bg);
+
+   build(e);
+
+   /* Several frames: the first one populates caches, and failures in the
+    * vector paths have tended to need a second pass to show up. */
+   for (i = 0; i < frames; i++)
+     ecore_evas_manual_render(ee);
+
+   /* A snapshot reads back uniformly whether the engine renders to memory
+    * or to a window. */
+   snap = evas_object_image_filled_add(e);
+   evas_object_image_snapshot_set(snap, EINA_TRUE);
+   evas_object_geometry_set(snap, 0, 0, WIN_W, WIN_H);
+   evas_object_show(snap);
+   ecore_evas_manual_render(ee);
+
+   evas_object_image_size_get(snap, &sw, NULL);
+   pixels = evas_object_image_data_get(snap, EINA_FALSE);
+   if (pixels && sw > 0) *out = pixels[py * sw + px];
+   else                  *out = 0;
+   if (pixels) evas_object_image_data_set(snap, pixels);
+
+   ecore_evas_free(ee);
+   return (pixels != NULL) && (sw > 0);
+}
+
+/* A narrow object followed by a much wider one, the wider one asking for
+ * group opacity.
+ *
+ * Both halves matter.  Every vector object on a canvas shares one ector
+ * surface, so the narrow object is what fixes that surface's stride, and the
+ * wide one then renders through it.  The group-opacity path additionally
+ * swaps the surface's pixel buffer out and back while it renders, which is
+ * what turned a mis-sized buffer into a use-after-free on the GL engine. */
+static void
+_build_narrow_then_wide(Evas *e)
+{
+   _vg_object_add(e, 0, 0, 16, 16, 255);
+   _vg_object_add(e, 32, 32, 192, 192, 128);
+}
+
+EFL_START_TEST(evas_vg_mixed_sizes_group_opacity)
+{
+   const char **eng;
+
+   for (eng = _engines; *eng; eng++)
+     {
+        unsigned int px = 0;
+
+        if (!_scene_sample(*eng, _build_narrow_then_wide, 4, 128, 128, &px))
+          continue;
+
+        /* Surviving this far is most of the point: before the buffer was
+         * sized and owned correctly, the GL engine wrote about a megabyte
+         * past a 57 KB allocation here and then crashed on a later frame. */
+        printf("engine %s: centre pixel %08x\n", *eng, px);
+     }
+}
+EFL_END_TEST
+
+/* Group opacity has to reach the framebuffer.
+ *
+ * An opaque white shape inside a container with alpha 128, over black, must
+ * land near 128 on every channel.  The GL engine used to composite the group
+ * into a buffer it never read back, so the group arrived fully opaque while
+ * the software engine had it right - a visible difference between engines
+ * for the same scene. */
+EFL_START_TEST(evas_vg_container_alpha_is_applied)
+{
+   const char **eng;
+
+   for (eng = _engines; *eng; eng++)
+     {
+        unsigned int px = 0;
+        int r;
+
+        if (!_scene_sample(*eng, _build_narrow_then_wide, 4, 128, 128, &px))
+          continue;
+
+        r = (px >> 16) & 0xff;
+        /* Wide tolerance: this is checking that the alpha was applied at
+         * all, not the exact rounding of the blend. */
+        ck_assert_msg(r > 100 && r < 160,
+                      "engine %s: group alpha 128 over black should give a "
+                      "channel near 128, got %d (pixel %08x)",
+                      *eng, r, px);
+     }
+}
+EFL_END_TEST
+
+void evas_test_vg(TCase *tc)
+{
+   tcase_add_test(tc, evas_vg_mixed_sizes_group_opacity);
+   tcase_add_test(tc, evas_vg_container_alpha_is_applied);
+}
diff --git a/src/tests/evas/meson.build b/src/tests/evas/meson.build
index 63670c7986..7b1861fecb 100644
--- a/src/tests/evas/meson.build
+++ b/src/tests/evas/meson.build
@@ -9,6 +9,7 @@ evas_suite_src = [
   'evas_test_text.c',
   'evas_test_callbacks.c',
   'evas_test_render_engines.c',
+  'evas_test_vg.c',
   'evas_test_filters.c',
   'evas_test_image.c',
   'evas_test_mask.c',

-- 
To stop receiving notification emails like this one, please contact
the administrator of this repository.

Reply via email to