summaryrefslogtreecommitdiff
path: root/test
diff options
context:
space:
mode:
authorEric Anholt <eric@anholt.net>2018-09-19 18:02:41 -0700
committerEric Anholt <eric@anholt.net>2018-09-25 21:27:08 -0700
commit89901e14d2f98def2399fd0546838721e7ac452d (patch)
treecc258e512c84f64ffa4153477c57182a507e769b /test
parentbc36594e0eb8bfa5a673bcfd8c8168f70994a1df (diff)
test: Add the start of a testsuite for damage.
Inspired by the previous bug, build something we can use to write damage testcases, including testing for the bug. Signed-off-by: Eric Anholt <eric@anholt.net>
Diffstat (limited to 'test')
-rw-r--r--test/damage/meson.build9
-rw-r--r--test/damage/primitives.c359
-rw-r--r--test/meson.build1
3 files changed, 369 insertions, 0 deletions
diff --git a/test/damage/meson.build b/test/damage/meson.build
new file mode 100644
index 000000000..091caff5f
--- /dev/null
+++ b/test/damage/meson.build
@@ -0,0 +1,9 @@
+xcb_dep = dependency('xcb', required: false)
+xcb_damage_dep = dependency('xcb-damage', required: false)
+
+if get_option('xvfb')
+ if xcb_dep.found() and xcb_damage_dep.found()
+ damage_primitives = executable('damage-primitives', 'primitives.c', dependencies: [xcb_dep, xcb_damage_dep])
+ test('damage-primitives', simple_xinit, args: [damage_primitives, '--', xvfb_server])
+ endif
+endif
diff --git a/test/damage/primitives.c b/test/damage/primitives.c
new file mode 100644
index 000000000..5ee63b689
--- /dev/null
+++ b/test/damage/primitives.c
@@ -0,0 +1,359 @@
+/*
+ * Copyright © 2018 Broadcom
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a
+ * copy of this software and associated documentation files (the "Software"),
+ * to deal in the Software without restriction, including without limitation
+ * the rights to use, copy, modify, merge, publish, distribute, sublicense,
+ * and/or sell copies of the Software, and to permit persons to whom the
+ * Software is furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice (including the next
+ * paragraph) shall be included in all copies or substantial portions of the
+ * Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
+ * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+ * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
+ * IN THE SOFTWARE.
+ */
+
+/** @file
+ *
+ * Touch-testing of the damage extension's implementation of various
+ * primitives. The core initializes the pixmap with some contents,
+ * turns on damage and each per-primitive test gets to just make a
+ * rendering call that draws some pixels. Afterwards, the core checks
+ * what pixels were modified and makes sure the damage report contains
+ * them.
+ */
+
+#include <assert.h>
+#include <stdbool.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <limits.h>
+#include <xcb/damage.h>
+
+struct test_setup {
+ xcb_connection_t *c;
+ xcb_drawable_t d;
+ xcb_drawable_t start_drawable;
+ uint32_t *start_drawable_contents;
+ xcb_screen_t *screen;
+ xcb_gc_t gc;
+ int width, height;
+ uint32_t *expected;
+ uint32_t *damaged;
+};
+
+#define ARRAY_SIZE(x) (sizeof(x) / sizeof((x)[0]))
+
+/**
+ * Performs a synchronous GetImage for a test pixmap, returning
+ * uint32_t per pixel.
+ */
+static uint32_t *
+get_image(struct test_setup *setup, xcb_drawable_t drawable)
+{
+ xcb_get_image_cookie_t cookie =
+ xcb_get_image(setup->c, XCB_IMAGE_FORMAT_Z_PIXMAP, drawable,
+ 0, 0, setup->width, setup->height, ~0);
+ xcb_get_image_reply_t *reply =
+ xcb_get_image_reply(setup->c, cookie, NULL);
+ uint8_t *data = xcb_get_image_data(reply);
+ int len = xcb_get_image_data_length(reply);
+
+ /* Do I understand X protocol and our test environment? */
+ assert(reply->depth == 24);
+ assert(len == 4 * setup->width * setup->height);
+
+ uint32_t *result = malloc(sizeof(uint32_t) *
+ setup->width * setup->height);
+ memcpy(result, data, len);
+ free(reply);
+
+ return result;
+}
+
+/**
+ * Gets the image drawn by the test and compares it to the starting
+ * image, producing a bitmask of what pixels were damaged.
+ */
+static void
+compute_expected_damage(struct test_setup *setup)
+{
+ uint32_t *results = get_image(setup, setup->d);
+ bool any_modified_pixels = false;
+
+ for (int i = 0; i < setup->width * setup->height; i++) {
+ if (results[i] != setup->start_drawable_contents[i]) {
+ setup->expected[i / 32] |= 1 << (i % 32);
+ any_modified_pixels = true;
+ }
+ }
+
+ /* Make sure that the testcases actually render something! */
+ assert(any_modified_pixels);
+}
+
+/**
+ * Processes a damage event, filling in the bitmask of pixels reported
+ * to be damaged.
+ */
+static bool
+damage_event_handle(struct test_setup *setup,
+ struct xcb_damage_notify_event_t *event)
+{
+ xcb_rectangle_t *rect = &event->area;
+ assert(event->drawable == setup->d);
+ for (int y = rect->y; y < rect->y + rect->height; y++) {
+ for (int x = rect->x; x < rect->x + rect->width; x++) {
+ int bit = y * setup->width + x;
+ setup->damaged[bit / 32] |= 1 << (bit % 32);
+ }
+ }
+
+ return event->level & 0x80; /* XXX: MORE is missing from xcb. */
+}
+
+/**
+ * Collects a series of damage events (while the MORE flag is set)
+ * into the damaged bitmask.
+ */
+static void
+collect_damage(struct test_setup *setup)
+{
+ const xcb_query_extension_reply_t *ext =
+ xcb_get_extension_data(setup->c, &xcb_damage_id);
+ xcb_generic_event_t *ge;
+
+ xcb_flush(setup->c);
+ while ((ge = xcb_wait_for_event(setup->c))) {
+ int event = ge->response_type & ~0x80;
+
+ if (event == (ext->first_event + XCB_DAMAGE_NOTIFY)) {
+ if (!damage_event_handle(setup,
+ (struct xcb_damage_notify_event_t *)ge)) {
+ return;
+ }
+ } else {
+ switch (ge->response_type) {
+ case 0: {
+ xcb_generic_error_t *error = (xcb_generic_error_t *)ge;
+ fprintf(stderr, "X error %d at sequence %d\n",
+ error->error_code, error->sequence);
+ exit(1);
+ }
+
+ case XCB_GRAPHICS_EXPOSURE:
+ case XCB_NO_EXPOSURE:
+ break;
+
+ default:
+ fprintf(stderr, "Unexpected event %d\n", ge->response_type);
+ exit(1);
+ }
+ }
+ }
+
+ fprintf(stderr, "I/O error\n");
+ exit(1);
+}
+
+/**
+ * Wrapper to set up the test pixmap, attach damage to it, and see if
+ * the reported damage matches the testcase's rendering.
+ */
+static bool
+damage_test(struct test_setup *setup,
+ void (*test)(struct test_setup *setup),
+ const char *name)
+{
+ uint32_t expected[32] = { 0 };
+ uint32_t damaged[32] = { 0 };
+
+ printf("Testing %s\n", name);
+
+ setup->expected = expected;
+ setup->damaged = damaged;
+
+ /* Create our pixmap for this test and fill it with the
+ * starting image.
+ */
+ setup->d = xcb_generate_id(setup->c);
+ xcb_create_pixmap(setup->c, setup->screen->root_depth,
+ setup->d, setup->screen->root,
+ setup->width, setup->height);
+
+ setup->gc = xcb_generate_id(setup->c);
+ uint32_t values[] = { setup->screen->black_pixel };
+ xcb_create_gc(setup->c, setup->gc, setup->screen->root,
+ XCB_GC_FOREGROUND, values);
+
+ xcb_copy_area(setup->c,
+ setup->start_drawable,
+ setup->d,
+ setup->gc,
+ 0, 0,
+ 0, 0,
+ setup->width, setup->height);
+
+ /* Start watching for damage now that we have the initial contents. */
+ xcb_damage_damage_t damage = xcb_generate_id(setup->c);
+ xcb_damage_create(setup->c, damage, setup->d,
+ XCB_DAMAGE_REPORT_LEVEL_RAW_RECTANGLES);
+
+ test(setup);
+
+ compute_expected_damage(setup);
+
+ xcb_damage_destroy(setup->c, damage);
+ xcb_free_gc(setup->c, setup->gc);
+ xcb_free_pixmap(setup->c, setup->d);
+ collect_damage(setup);
+
+ for (int bit = 0; bit < setup->width * setup->height; bit++) {
+ if ((expected[bit / 32] & (1 << bit % 32)) &&
+ !(damaged[bit / 32] & (1 << bit % 32))) {
+ fprintf(stderr, " fail: %s(): Damage report missed %d, %d\n",
+ name, bit % setup->width, bit / setup->width);
+ return false;
+ }
+ }
+
+ return true;
+}
+
+/**
+ * Creates the pixmap of contents that will be the initial state of
+ * each test's drawable.
+ */
+static void
+create_start_pixmap(struct test_setup *setup)
+{
+ setup->start_drawable = xcb_generate_id(setup->c);
+ xcb_create_pixmap(setup->c, setup->screen->root_depth,
+ setup->start_drawable, setup->screen->root,
+ setup->width, setup->height);
+
+ xcb_gc_t gc = xcb_generate_id(setup->c);
+ uint32_t values[] = { 0xaaaaaaaa };
+ xcb_create_gc(setup->c, gc, setup->screen->root,
+ XCB_GC_FOREGROUND, values);
+
+ xcb_rectangle_t rect = { 5, 5, 10, 15 };
+ xcb_poly_rectangle(setup->c, setup->start_drawable, gc, 1, &rect);
+
+ xcb_free_gc(setup->c, gc);
+
+ /* Capture the rendered start contents once, for comparing each
+ * test's rendering output to the start contents.
+ */
+ setup->start_drawable_contents = get_image(setup, setup->start_drawable);
+}
+
+static void
+test_poly_point_origin(struct test_setup *setup)
+{
+ struct xcb_point_t points[] = { {1, 2}, {3, 4} };
+ xcb_poly_point(setup->c, XCB_COORD_MODE_ORIGIN, setup->d, setup->gc,
+ ARRAY_SIZE(points), points);
+}
+
+static void
+test_poly_point_previous(struct test_setup *setup)
+{
+ struct xcb_point_t points[] = { {1, 2}, {3, 4} };
+ xcb_poly_point(setup->c, XCB_COORD_MODE_PREVIOUS, setup->d, setup->gc,
+ ARRAY_SIZE(points), points);
+}
+
+static void
+test_poly_line_origin(struct test_setup *setup)
+{
+ struct xcb_point_t points[] = { {1, 2}, {3, 4}, {5, 6} };
+ xcb_poly_line(setup->c, XCB_COORD_MODE_ORIGIN, setup->d, setup->gc,
+ ARRAY_SIZE(points), points);
+}
+
+static void
+test_poly_line_previous(struct test_setup *setup)
+{
+ struct xcb_point_t points[] = { {1, 2}, {3, 4}, {5, 6} };
+ xcb_poly_line(setup->c, XCB_COORD_MODE_PREVIOUS, setup->d, setup->gc,
+ ARRAY_SIZE(points), points);
+}
+
+static void
+test_poly_fill_rectangle(struct test_setup *setup)
+{
+ struct xcb_rectangle_t rects[] = { {1, 2, 3, 4},
+ {5, 6, 7, 8} };
+ xcb_poly_fill_rectangle(setup->c, setup->d, setup->gc,
+ ARRAY_SIZE(rects), rects);
+}
+
+static void
+test_poly_rectangle(struct test_setup *setup)
+{
+ struct xcb_rectangle_t rects[] = { {1, 2, 3, 4},
+ {5, 6, 7, 8} };
+ xcb_poly_rectangle(setup->c, setup->d, setup->gc,
+ ARRAY_SIZE(rects), rects);
+}
+
+static void
+test_poly_segment(struct test_setup *setup)
+{
+ struct xcb_segment_t segs[] = { {1, 2, 3, 4},
+ {5, 6, 7, 8} };
+ xcb_poly_segment(setup->c, setup->d, setup->gc,
+ ARRAY_SIZE(segs), segs);
+}
+
+int main(int argc, char **argv)
+{
+ int screen;
+ xcb_connection_t *c = xcb_connect(NULL, &screen);
+ const xcb_query_extension_reply_t *ext =
+ xcb_get_extension_data(c, &xcb_damage_id);
+
+ if (!ext->present) {
+ printf("No XDamage present\n");
+ exit(77);
+ }
+
+ struct test_setup setup = {
+ .c = c,
+ .width = 32,
+ .height = 32,
+ };
+
+ /* Get the screen so we have the root window. */
+ xcb_screen_iterator_t iter;
+ iter = xcb_setup_roots_iterator (xcb_get_setup (c));
+ setup.screen = iter.data;
+
+ xcb_damage_query_version(c, 1, 1);
+
+ create_start_pixmap(&setup);
+
+ bool pass = true;
+#define test(x) pass = damage_test(&setup, x, #x) && pass;
+
+ test(test_poly_point_origin);
+ test(test_poly_point_previous);
+ test(test_poly_line_origin);
+ test(test_poly_line_previous);
+ test(test_poly_fill_rectangle);
+ test(test_poly_rectangle);
+ test(test_poly_segment);
+
+ xcb_disconnect(c);
+ exit(pass ? 0 : 1);
+}
diff --git a/test/meson.build b/test/meson.build
index d413e9da5..33b7941e0 100644
--- a/test/meson.build
+++ b/test/meson.build
@@ -24,4 +24,5 @@ if get_option('xvfb')
endif
subdir('bigreq')
+subdir('damage')
subdir('sync')