blob: 15792e9eaf2d586f58a4978b44f03fcc49f3472a [file] [log] [blame]
Pekka Paalanenae12b952020-12-09 15:14:11 +02001/*
2 * Copyright © 2020 Collabora, Ltd.
3 *
4 * Permission is hereby granted, free of charge, to any person obtaining
5 * a copy of this software and associated documentation files (the
6 * "Software"), to deal in the Software without restriction, including
7 * without limitation the rights to use, copy, modify, merge, publish,
8 * distribute, sublicense, and/or sell copies of the Software, and to
9 * permit persons to whom the Software is furnished to do so, subject to
10 * the following conditions:
11 *
12 * The above copyright notice and this permission notice (including the
13 * next paragraph) shall be included in all copies or substantial
14 * portions of the Software.
15 *
16 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
17 * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
18 * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
19 * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS
20 * BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN
21 * ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
22 * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
23 * SOFTWARE.
24 */
25
26#include "config.h"
27
28#include <stdio.h>
29#include <string.h>
30#include <sys/mman.h>
31#include <math.h>
32#include <unistd.h>
33
Pekka Paalanenae12b952020-12-09 15:14:11 +020034#include "weston-test-client-helper.h"
35#include "weston-test-fixture-compositor.h"
36#include "shared/os-compatibility.h"
Pekka Paalanen4b301fe2021-02-04 17:39:45 +020037#include "shared/weston-drm-fourcc.h"
Pekka Paalanenae12b952020-12-09 15:14:11 +020038#include "shared/xalloc.h"
39
40static enum test_result_code
41fixture_setup(struct weston_test_harness *harness)
42{
43 struct compositor_setup setup;
44
45 compositor_setup_defaults(&setup);
46 setup.renderer = RENDERER_GL;
47 setup.width = 324;
48 setup.height = 264;
49 setup.shell = SHELL_TEST_DESKTOP;
Harish Krupo7ef26882019-04-18 21:45:48 +053050 setup.logging_scopes = "log,gl-shader-generator";
Pekka Paalanenae12b952020-12-09 15:14:11 +020051
52 return weston_test_harness_execute_as_client(harness, &setup);
53}
54DECLARE_FIXTURE_SETUP(fixture_setup);
55
56struct yuv_buffer {
57 void *data;
58 size_t bytes;
59 struct wl_buffer *proxy;
60 int width;
61 int height;
62};
63
64struct yuv_case {
65 uint32_t drm_format;
66 const char *drm_format_name;
67 struct yuv_buffer *(*create_buffer)(struct client *client,
68 uint32_t drm_format,
69 pixman_image_t *rgb_image);
70};
71
72static struct yuv_buffer *
73yuv_buffer_create(struct client *client,
74 size_t bytes,
75 int width,
76 int height,
77 int stride_bytes,
78 uint32_t drm_format)
79{
80 struct wl_shm_pool *pool;
81 struct yuv_buffer *buf;
82 int fd;
83
84 buf = xzalloc(sizeof *buf);
85 buf->bytes = bytes;
86 buf->width = width;
87 buf->height = height;
88
89 fd = os_create_anonymous_file(buf->bytes);
90 assert(fd >= 0);
91
92 buf->data = mmap(NULL, buf->bytes,
93 PROT_READ | PROT_WRITE, MAP_SHARED, fd, 0);
94 if (buf->data == MAP_FAILED) {
95 close(fd);
96 assert(buf->data != MAP_FAILED);
97 }
98
99 pool = wl_shm_create_pool(client->wl_shm, fd, buf->bytes);
100 buf->proxy = wl_shm_pool_create_buffer(pool, 0, buf->width, buf->height,
101 stride_bytes, drm_format);
102 wl_shm_pool_destroy(pool);
103 close(fd);
104
105 return buf;
106}
107
108static void
109yuv_buffer_destroy(struct yuv_buffer *buf)
110{
111 wl_buffer_destroy(buf->proxy);
112 assert(munmap(buf->data, buf->bytes) == 0);
113 free(buf);
114}
115
116/*
117 * Based on Rec. ITU-R BT.601-7
118 *
119 * This is intended to be obvious and accurate, not fast.
120 */
121static void
122x8r8g8b8_to_ycbcr8_bt601(uint32_t xrgb,
123 uint8_t *y_out, uint8_t *cb_out, uint8_t *cr_out)
124{
125 double y, cb, cr;
126 double r = (xrgb >> 16) & 0xff;
127 double g = (xrgb >> 8) & 0xff;
128 double b = (xrgb >> 0) & 0xff;
129
130 /* normalize to [0.0, 1.0] */
131 r /= 255.0;
132 g /= 255.0;
133 b /= 255.0;
134
135 /* Y normalized to [0.0, 1.0], Cb and Cr [-0.5, 0.5] */
136 y = 0.299 * r + 0.587 * g + 0.114 * b;
137 cr = (r - y) / 1.402;
138 cb = (b - y) / 1.772;
139
140 /* limited range quantization to 8 bit */
141 *y_out = round(219.0 * y + 16.0);
142 if (cr_out)
143 *cr_out = round(224.0 * cr + 128.0);
144 if (cb_out)
145 *cb_out = round(224.0 * cb + 128.0);
146}
147
148/*
149 * 3 plane YCbCr
150 * plane 0: Y plane, [7:0] Y
151 * plane 1: Cb plane, [7:0] Cb
152 * plane 2: Cr plane, [7:0] Cr
153 * 2x2 subsampled Cb (1) and Cr (2) planes
154 */
155static struct yuv_buffer *
156yuv420_create_buffer(struct client *client,
157 uint32_t drm_format,
158 pixman_image_t *rgb_image)
159{
160 struct yuv_buffer *buf;
161 size_t bytes;
162 int width;
163 int height;
164 int x, y;
165 void *rgb_pixels;
166 int rgb_stride_bytes;
167 uint32_t *rgb_row;
168 uint8_t *y_base;
169 uint8_t *u_base;
170 uint8_t *v_base;
171 uint8_t *y_row;
172 uint8_t *u_row;
173 uint8_t *v_row;
174 uint32_t argb;
175
176 assert(drm_format == DRM_FORMAT_YUV420);
177
178 width = pixman_image_get_width(rgb_image);
179 height = pixman_image_get_height(rgb_image);
180 rgb_pixels = pixman_image_get_data(rgb_image);
181 rgb_stride_bytes = pixman_image_get_stride(rgb_image);
182
183 /* Full size Y, quarter U and V */
184 bytes = width * height + (width / 2) * (height / 2) * 2;
185 buf = yuv_buffer_create(client, bytes, width, height, width, drm_format);
186
187 y_base = buf->data;
188 u_base = y_base + width * height;
189 v_base = u_base + (width / 2) * (height / 2);
190
191 for (y = 0; y < height; y++) {
192 rgb_row = rgb_pixels + (y / 2 * 2) * rgb_stride_bytes;
193 y_row = y_base + y * width;
194 u_row = u_base + (y / 2) * (width / 2);
195 v_row = v_base + (y / 2) * (width / 2);
196
197 for (x = 0; x < width; x++) {
198 /*
199 * Sub-sample the source image instead, so that U and V
200 * sub-sampling does not require proper
201 * filtering/averaging/siting.
202 */
203 argb = *(rgb_row + x / 2 * 2);
204
205 /*
206 * A stupid way of "sub-sampling" chroma. This does not
207 * do the necessary filtering/averaging/siting or
208 * alternate Cb/Cr rows.
209 */
210 if ((y & 1) == 0 && (x & 1) == 0) {
211 x8r8g8b8_to_ycbcr8_bt601(argb, y_row + x,
212 u_row + x / 2,
213 v_row + x / 2);
214 } else {
215 x8r8g8b8_to_ycbcr8_bt601(argb, y_row + x,
216 NULL, NULL);
217 }
218 }
219 }
220
221 return buf;
222}
223
224/*
225 * 2 plane YCbCr
226 * plane 0 = Y plane, [7:0] Y
227 * plane 1 = Cr:Cb plane, [15:0] Cr:Cb little endian
228 * 2x2 subsampled Cr:Cb plane
229 */
230static struct yuv_buffer *
231nv12_create_buffer(struct client *client,
232 uint32_t drm_format,
233 pixman_image_t *rgb_image)
234{
235 struct yuv_buffer *buf;
236 size_t bytes;
237 int width;
238 int height;
239 int x, y;
240 void *rgb_pixels;
241 int rgb_stride_bytes;
242 uint32_t *rgb_row;
243 uint8_t *y_base;
244 uint16_t *uv_base;
245 uint8_t *y_row;
246 uint16_t *uv_row;
247 uint32_t argb;
248 uint8_t cr;
249 uint8_t cb;
250
251 assert(drm_format == DRM_FORMAT_NV12);
252
253 width = pixman_image_get_width(rgb_image);
254 height = pixman_image_get_height(rgb_image);
255 rgb_pixels = pixman_image_get_data(rgb_image);
256 rgb_stride_bytes = pixman_image_get_stride(rgb_image);
257
258 /* Full size Y, quarter UV */
259 bytes = width * height + (width / 2) * (height / 2) * sizeof(uint16_t);
260 buf = yuv_buffer_create(client, bytes, width, height, width, drm_format);
261
262 y_base = buf->data;
263 uv_base = (uint16_t *)(y_base + width * height);
264
265 for (y = 0; y < height; y++) {
266 rgb_row = rgb_pixels + (y / 2 * 2) * rgb_stride_bytes;
267 y_row = y_base + y * width;
268 uv_row = uv_base + (y / 2) * (width / 2);
269
270 for (x = 0; x < width; x++) {
271 /*
272 * Sub-sample the source image instead, so that U and V
273 * sub-sampling does not require proper
274 * filtering/averaging/siting.
275 */
276 argb = *(rgb_row + x / 2 * 2);
277
278 /*
279 * A stupid way of "sub-sampling" chroma. This does not
280 * do the necessary filtering/averaging/siting.
281 */
282 if ((y & 1) == 0 && (x & 1) == 0) {
283 x8r8g8b8_to_ycbcr8_bt601(argb, y_row + x,
284 &cb, &cr);
285 *(uv_row + x / 2) = ((uint16_t)cr << 8) | cb;
286 } else {
287 x8r8g8b8_to_ycbcr8_bt601(argb, y_row + x,
288 NULL, NULL);
289 }
290 }
291 }
292
293 return buf;
294}
295
296/*
297 * Packed YCbCr
298 *
299 * [31:0] Cr0:Y1:Cb0:Y0 8:8:8:8 little endian
300 * 2x1 subsampled Cr:Cb plane
301 */
302static struct yuv_buffer *
303yuyv_create_buffer(struct client *client,
304 uint32_t drm_format,
305 pixman_image_t *rgb_image)
306{
307 struct yuv_buffer *buf;
308 size_t bytes;
309 int width;
310 int height;
311 int x, y;
312 void *rgb_pixels;
313 int rgb_stride_bytes;
314 uint32_t *rgb_row;
315 uint32_t *yuv_base;
316 uint32_t *yuv_row;
317 uint8_t cr;
318 uint8_t cb;
319 uint8_t y0;
320
321 assert(drm_format == DRM_FORMAT_YUYV);
322
323 width = pixman_image_get_width(rgb_image);
324 height = pixman_image_get_height(rgb_image);
325 rgb_pixels = pixman_image_get_data(rgb_image);
326 rgb_stride_bytes = pixman_image_get_stride(rgb_image);
327
328 /* Full size Y, horizontally subsampled UV, 2 pixels in 32 bits */
329 bytes = width / 2 * height * sizeof(uint32_t);
330 buf = yuv_buffer_create(client, bytes, width, height, width / 2 * sizeof(uint32_t), drm_format);
331
332 yuv_base = buf->data;
333
334 for (y = 0; y < height; y++) {
335 rgb_row = rgb_pixels + (y / 2 * 2) * rgb_stride_bytes;
336 yuv_row = yuv_base + y * (width / 2);
337
338 for (x = 0; x < width; x += 2) {
339 /*
340 * Sub-sample the source image instead, so that U and V
341 * sub-sampling does not require proper
342 * filtering/averaging/siting.
343 */
344 x8r8g8b8_to_ycbcr8_bt601(*(rgb_row + x), &y0, &cb, &cr);
345 *(yuv_row + x / 2) =
346 ((uint32_t)cr << 24) |
347 ((uint32_t)y0 << 16) |
348 ((uint32_t)cb << 8) |
349 ((uint32_t)y0 << 0);
350 }
351 }
352
353 return buf;
354}
355
Pekka Paalanenf6a8aa12021-02-05 16:30:22 +0200356/*
357 * Packed YCbCr
358 *
359 * [31:0] X:Y:Cb:Cr 8:8:8:8 little endian
360 * full resolution chroma
361 */
362static struct yuv_buffer *
363xyuv8888_create_buffer(struct client *client,
364 uint32_t drm_format,
365 pixman_image_t *rgb_image)
366{
367 struct yuv_buffer *buf;
368 size_t bytes;
369 int width;
370 int height;
371 int x, y;
372 void *rgb_pixels;
373 int rgb_stride_bytes;
374 uint32_t *rgb_row;
375 uint32_t *yuv_base;
376 uint32_t *yuv_row;
377 uint8_t cr;
378 uint8_t cb;
379 uint8_t y0;
380
381 assert(drm_format == DRM_FORMAT_XYUV8888);
382
383 width = pixman_image_get_width(rgb_image);
384 height = pixman_image_get_height(rgb_image);
385 rgb_pixels = pixman_image_get_data(rgb_image);
386 rgb_stride_bytes = pixman_image_get_stride(rgb_image);
387
388 /* Full size, 32 bits per pixel */
389 bytes = width * height * sizeof(uint32_t);
390 buf = yuv_buffer_create(client, bytes, width, height, width * sizeof(uint32_t), drm_format);
391
392 yuv_base = buf->data;
393
394 for (y = 0; y < height; y++) {
395 rgb_row = rgb_pixels + (y / 2 * 2) * rgb_stride_bytes;
396 yuv_row = yuv_base + y * width;
397
398 for (x = 0; x < width; x++) {
399 /*
400 * 2x2 sub-sample the source image to get the same
401 * result as the other YUV variants, so we can use the
402 * same reference image for checking.
403 */
404 x8r8g8b8_to_ycbcr8_bt601(*(rgb_row + x / 2 * 2), &y0, &cb, &cr);
405 /*
406 * The unused byte is intentionally set to "garbage"
407 * to catch any accidental use of it in the compositor.
408 */
409 *(yuv_row + x) =
410 ((uint32_t)x << 24) |
411 ((uint32_t)y0 << 16) |
412 ((uint32_t)cb << 8) |
413 ((uint32_t)cr << 0);
414 }
415 }
416
417 return buf;
418}
419
Pekka Paalanenae12b952020-12-09 15:14:11 +0200420static void
421show_window_with_yuv(struct client *client, struct yuv_buffer *buf)
422{
423 struct surface *surface = client->surface;
424 int done;
425
426 weston_test_move_surface(client->test->weston_test, surface->wl_surface,
427 4, 4);
428 wl_surface_attach(surface->wl_surface, buf->proxy, 0, 0);
429 wl_surface_damage(surface->wl_surface, 0, 0, buf->width,
430 buf->height);
431 frame_callback_set(surface->wl_surface, &done);
432 wl_surface_commit(surface->wl_surface);
433 frame_callback_wait(client, &done);
434}
435
436static const struct yuv_case yuv_cases[] = {
437#define FMT(x) DRM_FORMAT_ ##x, #x
438 { FMT(YUV420), yuv420_create_buffer },
439 { FMT(NV12), nv12_create_buffer },
440 { FMT(YUYV), yuyv_create_buffer },
Pekka Paalanenf6a8aa12021-02-05 16:30:22 +0200441 { FMT(XYUV8888), xyuv8888_create_buffer },
Pekka Paalanenae12b952020-12-09 15:14:11 +0200442#undef FMT
443};
444
445/*
446 * Test that various YUV pixel formats result in correct coloring on screen.
447 */
448TEST_P(yuv_buffer_shm, yuv_cases)
449{
450 const struct yuv_case *my_case = data;
451 char *fname;
452 pixman_image_t *img;
453 struct client *client;
454 struct yuv_buffer *buf;
455 bool match;
456
457 testlog("%s: format %s\n", get_test_name(), my_case->drm_format_name);
458
459 /*
460 * This test image is 256 x 256 pixels.
461 *
462 * Therefore this test does NOT exercise:
463 * - odd image dimensions
464 * - non-square image
465 * - row padding
466 * - unaligned row stride
467 * - different alignments or padding in sub-sampled planes
468 *
469 * The reason to not test these is that GL-renderer seems to be more
470 * or less broken.
471 *
472 * The source image is effectively further downscaled to 128 x 128
473 * before sampled and converted to 256 x 256 YUV, so that
474 * sub-sampling for U and V does not require proper algorithms.
475 * Therefore, this test also does not test:
476 * - chroma siting (chroma sample positioning)
477 */
478 fname = image_filename("chocolate-cake");
479 img = load_image_from_png(fname);
480 free(fname);
481 assert(img);
482
483 client = create_client();
484 client->surface = create_test_surface(client);
485 buf = my_case->create_buffer(client, my_case->drm_format, img);
486 show_window_with_yuv(client, buf);
487
488 match = verify_screen_content(client, "yuv-buffer", 0, NULL, 0);
489 assert(match);
490
491 yuv_buffer_destroy(buf);
492 client_destroy(client);
493}