1/*
2 * Copyright © 2018 Broadcom
3 *
4 * Permission is hereby granted, free of charge, to any person obtaining a
5 * copy of this software and associated documentation files (the "Software"),
6 * to deal in the Software without restriction, including without limitation
7 * the rights to use, copy, modify, merge, publish, distribute, sublicense,
8 * and/or sell copies of the Software, and to permit persons to whom the
9 * Software is furnished to do so, subject to the following conditions:
10 *
11 * The above copyright notice and this permission notice (including the next
12 * paragraph) shall be included in all copies or substantial portions of the
13 * Software.
14 *
15 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.  IN NO EVENT SHALL
18 * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
20 * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
21 * IN THE SOFTWARE.
22 */
23
24/** @file
25 *
26 * Touch-testing of the damage extension's implementation of various
27 * primitives.  The core initializes the pixmap with some contents,
28 * turns on damage and each per-primitive test gets to just make a
29 * rendering call that draws some pixels.  Afterwards, the core checks
30 * what pixels were modified and makes sure the damage report contains
31 * them.
32 */
33
34/* Test relies on assert() */
35#undef NDEBUG
36
37#include <assert.h>
38#include <stdbool.h>
39#include <stdio.h>
40#include <stdlib.h>
41#include <string.h>
42#include <limits.h>
43#include <xcb/damage.h>
44
45struct test_setup {
46    xcb_connection_t *c;
47    xcb_drawable_t d;
48    xcb_drawable_t start_drawable;
49    uint32_t *start_drawable_contents;
50    xcb_screen_t *screen;
51    xcb_gc_t gc;
52    int width, height;
53    uint32_t *expected;
54    uint32_t *damaged;
55};
56
57#define ARRAY_SIZE(x) (sizeof(x) / sizeof((x)[0]))
58
59/**
60 * Performs a synchronous GetImage for a test pixmap, returning
61 * uint32_t per pixel.
62 */
63static uint32_t *
64get_image(struct test_setup *setup, xcb_drawable_t drawable)
65{
66    xcb_get_image_cookie_t cookie =
67        xcb_get_image(setup->c, XCB_IMAGE_FORMAT_Z_PIXMAP, drawable,
68                      0, 0, setup->width, setup->height, ~0);
69    xcb_get_image_reply_t *reply =
70        xcb_get_image_reply(setup->c, cookie, NULL);
71    uint8_t *data = xcb_get_image_data(reply);
72    int len = xcb_get_image_data_length(reply);
73
74    /* Do I understand X protocol and our test environment? */
75    assert(reply->depth == 24);
76    assert(len == 4 * setup->width * setup->height);
77
78    uint32_t *result = malloc(sizeof(uint32_t) *
79                              setup->width * setup->height);
80    memcpy(result, data, len);
81    free(reply);
82
83    return result;
84}
85
86/**
87 * Gets the image drawn by the test and compares it to the starting
88 * image, producing a bitmask of what pixels were damaged.
89 */
90static void
91compute_expected_damage(struct test_setup *setup)
92{
93    uint32_t *results = get_image(setup, setup->d);
94    bool any_modified_pixels = false;
95
96    for (int i = 0; i < setup->width * setup->height; i++) {
97        if (results[i] != setup->start_drawable_contents[i]) {
98            setup->expected[i / 32] |= 1 << (i % 32);
99            any_modified_pixels = true;
100        }
101    }
102
103    /* Make sure that the testcases actually render something! */
104    assert(any_modified_pixels);
105}
106
107/**
108 * Processes a damage event, filling in the bitmask of pixels reported
109 * to be damaged.
110 */
111static bool
112damage_event_handle(struct test_setup *setup,
113                    struct xcb_damage_notify_event_t *event)
114{
115    xcb_rectangle_t *rect = &event->area;
116    assert(event->drawable == setup->d);
117    for (int y = rect->y; y < rect->y + rect->height; y++) {
118        for (int x = rect->x; x < rect->x + rect->width; x++) {
119            int bit = y * setup->width + x;
120            setup->damaged[bit / 32] |= 1 << (bit % 32);
121        }
122    }
123
124    return event->level & 0x80; /* XXX: MORE is missing from xcb. */
125}
126
127/**
128 * Collects a series of damage events (while the MORE flag is set)
129 * into the damaged bitmask.
130 */
131static void
132collect_damage(struct test_setup *setup)
133{
134    const xcb_query_extension_reply_t *ext =
135        xcb_get_extension_data(setup->c, &xcb_damage_id);
136    xcb_generic_event_t *ge;
137
138    xcb_flush(setup->c);
139    while ((ge = xcb_wait_for_event(setup->c))) {
140        int event = ge->response_type & ~0x80;
141
142        if (event == (ext->first_event + XCB_DAMAGE_NOTIFY)) {
143            if (!damage_event_handle(setup,
144                                     (struct xcb_damage_notify_event_t *)ge)) {
145                return;
146            }
147        } else {
148            switch (ge->response_type) {
149            case 0: {
150                xcb_generic_error_t *error = (xcb_generic_error_t *)ge;
151                fprintf(stderr, "X error %d at sequence %d\n",
152                        error->error_code, error->sequence);
153                exit(1);
154            }
155
156            case XCB_GRAPHICS_EXPOSURE:
157            case XCB_NO_EXPOSURE:
158                break;
159
160            default:
161                fprintf(stderr, "Unexpected event %d\n", ge->response_type);
162                exit(1);
163            }
164        }
165    }
166
167    fprintf(stderr, "I/O error\n");
168    exit(1);
169}
170
171/**
172 * Wrapper to set up the test pixmap, attach damage to it, and see if
173 * the reported damage matches the testcase's rendering.
174 */
175static bool
176damage_test(struct test_setup *setup,
177            void (*test)(struct test_setup *setup),
178            const char *name)
179{
180    uint32_t expected[32] = { 0 };
181    uint32_t damaged[32] = { 0 };
182
183    printf("Testing %s\n", name);
184
185    setup->expected = expected;
186    setup->damaged = damaged;
187
188    /* Create our pixmap for this test and fill it with the
189     * starting image.
190     */
191    setup->d = xcb_generate_id(setup->c);
192    xcb_create_pixmap(setup->c, setup->screen->root_depth,
193                      setup->d, setup->screen->root,
194                      setup->width, setup->height);
195
196    setup->gc = xcb_generate_id(setup->c);
197    uint32_t values[]  = { setup->screen->black_pixel };
198    xcb_create_gc(setup->c, setup->gc, setup->screen->root,
199                  XCB_GC_FOREGROUND, values);
200
201    xcb_copy_area(setup->c,
202                  setup->start_drawable,
203                  setup->d,
204                  setup->gc,
205                  0, 0,
206                  0, 0,
207                  setup->width, setup->height);
208
209    /* Start watching for damage now that we have the initial contents. */
210    xcb_damage_damage_t damage = xcb_generate_id(setup->c);
211    xcb_damage_create(setup->c, damage, setup->d,
212                      XCB_DAMAGE_REPORT_LEVEL_RAW_RECTANGLES);
213
214    test(setup);
215
216    compute_expected_damage(setup);
217
218    xcb_damage_destroy(setup->c, damage);
219    xcb_free_gc(setup->c, setup->gc);
220    xcb_free_pixmap(setup->c, setup->d);
221    collect_damage(setup);
222
223    for (int bit = 0; bit < setup->width * setup->height; bit++) {
224        if ((expected[bit / 32] & (1 << bit %  32)) &&
225            !(damaged[bit / 32] & (1 << bit %  32))) {
226            fprintf(stderr, "  fail: %s(): Damage report missed %d, %d\n",
227                    name, bit % setup->width, bit / setup->width);
228            return false;
229        }
230    }
231
232    return true;
233}
234
235/**
236 * Creates the pixmap of contents that will be the initial state of
237 * each test's drawable.
238 */
239static void
240create_start_pixmap(struct test_setup *setup)
241{
242    setup->start_drawable = xcb_generate_id(setup->c);
243    xcb_create_pixmap(setup->c, setup->screen->root_depth,
244                      setup->start_drawable, setup->screen->root,
245                      setup->width, setup->height);
246
247    /* Fill pixmap so it has defined contents */
248    xcb_gc_t fill = xcb_generate_id(setup->c);
249    uint32_t fill_values[]  = { setup->screen->white_pixel };
250    xcb_create_gc(setup->c, fill, setup->screen->root,
251                  XCB_GC_FOREGROUND, fill_values);
252
253    xcb_rectangle_t rect_all = { 0, 0, setup->width, setup->height};
254    xcb_poly_fill_rectangle(setup->c, setup->start_drawable,
255                            fill, 1, &rect_all);
256    xcb_free_gc(setup->c, fill);
257
258    /* Draw a rectangle */
259    xcb_gc_t gc = xcb_generate_id(setup->c);
260    uint32_t values[]  = { 0xaaaaaaaa };
261    xcb_create_gc(setup->c, gc, setup->screen->root,
262                  XCB_GC_FOREGROUND, values);
263
264    xcb_rectangle_t rect = { 5, 5, 10, 15 };
265    xcb_poly_rectangle(setup->c, setup->start_drawable, gc, 1, &rect);
266
267    xcb_free_gc(setup->c, gc);
268
269    /* Capture the rendered start contents once, for comparing each
270     * test's rendering output to the start contents.
271     */
272    setup->start_drawable_contents = get_image(setup, setup->start_drawable);
273}
274
275static void
276test_poly_point_origin(struct test_setup *setup)
277{
278    struct xcb_point_t points[] = { {1, 2}, {3, 4} };
279    xcb_poly_point(setup->c, XCB_COORD_MODE_ORIGIN, setup->d, setup->gc,
280                   ARRAY_SIZE(points), points);
281}
282
283static void
284test_poly_point_previous(struct test_setup *setup)
285{
286    struct xcb_point_t points[] = { {1, 2}, {3, 4} };
287    xcb_poly_point(setup->c, XCB_COORD_MODE_PREVIOUS, setup->d, setup->gc,
288                   ARRAY_SIZE(points), points);
289}
290
291static void
292test_poly_line_origin(struct test_setup *setup)
293{
294    struct xcb_point_t points[] = { {1, 2}, {3, 4}, {5, 6} };
295    xcb_poly_line(setup->c, XCB_COORD_MODE_ORIGIN, setup->d, setup->gc,
296                   ARRAY_SIZE(points), points);
297}
298
299static void
300test_poly_line_previous(struct test_setup *setup)
301{
302    struct xcb_point_t points[] = { {1, 2}, {3, 4}, {5, 6} };
303    xcb_poly_line(setup->c, XCB_COORD_MODE_PREVIOUS, setup->d, setup->gc,
304                   ARRAY_SIZE(points), points);
305}
306
307static void
308test_poly_fill_rectangle(struct test_setup *setup)
309{
310    struct xcb_rectangle_t rects[] = { {1, 2, 3, 4},
311                                       {5, 6, 7, 8} };
312    xcb_poly_fill_rectangle(setup->c, setup->d, setup->gc,
313                   ARRAY_SIZE(rects), rects);
314}
315
316static void
317test_poly_rectangle(struct test_setup *setup)
318{
319    struct xcb_rectangle_t rects[] = { {1, 2, 3, 4},
320                                       {5, 6, 7, 8} };
321    xcb_poly_rectangle(setup->c, setup->d, setup->gc,
322                       ARRAY_SIZE(rects), rects);
323}
324
325static void
326test_poly_segment(struct test_setup *setup)
327{
328    struct xcb_segment_t segs[] = { {1, 2, 3, 4},
329                                    {5, 6, 7, 8} };
330    xcb_poly_segment(setup->c, setup->d, setup->gc,
331                     ARRAY_SIZE(segs), segs);
332}
333
334int main(int argc, char **argv)
335{
336    int screen;
337    xcb_connection_t *c = xcb_connect(NULL, &screen);
338    const xcb_query_extension_reply_t *ext =
339        xcb_get_extension_data(c, &xcb_damage_id);
340
341    if (!ext->present) {
342        printf("No XDamage present\n");
343        exit(77);
344    }
345
346    struct test_setup setup = {
347        .c = c,
348        .width = 32,
349        .height = 32,
350    };
351
352    /* Get the screen so we have the root window. */
353    xcb_screen_iterator_t iter;
354    iter = xcb_setup_roots_iterator (xcb_get_setup (c));
355    setup.screen = iter.data;
356
357    xcb_damage_query_version(c, 1, 1);
358
359    create_start_pixmap(&setup);
360
361    bool pass = true;
362#define test(x) pass = damage_test(&setup, x, #x) && pass;
363
364    test(test_poly_point_origin);
365    test(test_poly_point_previous);
366    test(test_poly_line_origin);
367    test(test_poly_line_previous);
368    test(test_poly_fill_rectangle);
369    test(test_poly_rectangle);
370    test(test_poly_segment);
371
372    xcb_disconnect(c);
373    exit(pass ? 0 : 1);
374}
375