Branch data Line data Source code
1 : : #include "gpu_tests.h"
2 : : #include "shaders.h"
3 : :
4 : : #include <libplacebo/renderer.h>
5 : : #include <libplacebo/utils/frame_queue.h>
6 : : #include <libplacebo/utils/upload.h>
7 : :
8 : : //#define PRINT_OUTPUT
9 : :
10 : 6 : void pl_buffer_tests(pl_gpu gpu)
11 : : {
12 : : const size_t buf_size = 1024;
13 [ + + ]: 6 : if (buf_size > gpu->limits.max_buf_size)
14 : 1 : return;
15 : : printf("pl_buffer_tests:\n");
16 : :
17 : 5 : uint8_t *test_src = malloc(buf_size * 2);
18 : 5 : uint8_t *test_dst = test_src + buf_size;
19 [ - + ]: 5 : assert(test_src && test_dst);
20 : : memset(test_dst, 0, buf_size);
21 [ + + ]: 5125 : for (int i = 0; i < buf_size; i++)
22 : 5120 : test_src[i] = RANDOM_U8;
23 : :
24 : 5 : pl_buf buf = NULL, tbuf = NULL;
25 : :
26 : : printf("- test buffer static creation and readback\n");
27 : 5 : buf = pl_buf_create(gpu, pl_buf_params(
28 : : .size = buf_size,
29 : : .host_readable = true,
30 : : .initial_data = test_src,
31 : : ));
32 : :
33 [ - + ]: 5 : REQUIRE(buf);
34 [ - + ]: 5 : REQUIRE(pl_buf_read(gpu, buf, 0, test_dst, buf_size));
35 : 5 : REQUIRE_MEMEQ(test_src, test_dst, buf_size);
36 : 5 : pl_buf_destroy(gpu, &buf);
37 : :
38 : : printf("- test buffer empty creation, update and readback\n");
39 : : memset(test_dst, 0, buf_size);
40 : 5 : buf = pl_buf_create(gpu, pl_buf_params(
41 : : .size = buf_size,
42 : : .host_writable = true,
43 : : .host_readable = true,
44 : : ));
45 : :
46 [ - + ]: 5 : REQUIRE(buf);
47 : 5 : pl_buf_write(gpu, buf, 0, test_src, buf_size);
48 [ - + ]: 5 : REQUIRE(pl_buf_read(gpu, buf, 0, test_dst, buf_size));
49 : 5 : REQUIRE_MEMEQ(test_src, test_dst, buf_size);
50 : 5 : pl_buf_destroy(gpu, &buf);
51 : :
52 : : printf("- test buffer-buffer copy and readback\n");
53 : : memset(test_dst, 0, buf_size);
54 : 5 : buf = pl_buf_create(gpu, pl_buf_params(
55 : : .size = buf_size,
56 : : .initial_data = test_src,
57 : : ));
58 : :
59 : 5 : tbuf = pl_buf_create(gpu, pl_buf_params(
60 : : .size = buf_size,
61 : : .host_readable = true,
62 : : ));
63 : :
64 [ + - - + ]: 5 : REQUIRE(buf && tbuf);
65 : 5 : pl_buf_copy(gpu, tbuf, 0, buf, 0, buf_size);
66 [ - + ]: 5 : REQUIRE(pl_buf_read(gpu, tbuf, 0, test_dst, buf_size));
67 : 5 : REQUIRE_MEMEQ(test_src, test_dst, buf_size);
68 : 5 : pl_buf_destroy(gpu, &buf);
69 : 5 : pl_buf_destroy(gpu, &tbuf);
70 : :
71 [ + - ]: 5 : if (buf_size <= gpu->limits.max_mapped_size) {
72 : : printf("- test host mapped buffer readback\n");
73 : 5 : buf = pl_buf_create(gpu, pl_buf_params(
74 : : .size = buf_size,
75 : : .host_mapped = true,
76 : : .initial_data = test_src,
77 : : ));
78 : :
79 [ - + ]: 5 : REQUIRE(buf);
80 [ - + ]: 5 : REQUIRE(!pl_buf_poll(gpu, buf, 0));
81 : 5 : REQUIRE_MEMEQ(test_src, buf->data, buf_size);
82 : 5 : pl_buf_destroy(gpu, &buf);
83 : : }
84 : :
85 : : // `compute_queues` check is to exclude dummy GPUs here
86 [ + + + + ]: 5 : if (buf_size <= gpu->limits.max_ssbo_size && gpu->limits.compute_queues)
87 : : {
88 : : printf("- test endian swapping\n");
89 : 2 : buf = pl_buf_create(gpu, pl_buf_params(
90 : : .size = buf_size,
91 : : .storable = true,
92 : : .initial_data = test_src,
93 : : ));
94 : :
95 : 2 : tbuf = pl_buf_create(gpu, pl_buf_params(
96 : : .size = buf_size,
97 : : .storable = true,
98 : : .host_readable = true,
99 : : ));
100 : :
101 [ + - - + ]: 2 : REQUIRE(buf && tbuf);
102 [ - + ]: 2 : REQUIRE(pl_buf_copy_swap(gpu, &(struct pl_buf_copy_swap_params) {
103 : : .src = buf,
104 : : .dst = tbuf,
105 : : .size = buf_size,
106 : : .wordsize = 2,
107 : : }));
108 [ - + ]: 2 : REQUIRE(pl_buf_read(gpu, tbuf, 0, test_dst, buf_size));
109 [ + + ]: 1026 : for (int i = 0; i < buf_size / 2; i++) {
110 [ - + ]: 1024 : REQUIRE_CMP(test_src[2 * i + 0], ==, test_dst[2 * i + 1], PRIu8);
111 [ - + ]: 1024 : REQUIRE_CMP(test_src[2 * i + 1], ==, test_dst[2 * i + 0], PRIu8);
112 : : }
113 : : // test endian swap in-place
114 [ - + ]: 2 : REQUIRE(pl_buf_copy_swap(gpu, &(struct pl_buf_copy_swap_params) {
115 : : .src = tbuf,
116 : : .dst = tbuf,
117 : : .size = buf_size,
118 : : .wordsize = 4,
119 : : }));
120 [ - + ]: 2 : REQUIRE(pl_buf_read(gpu, tbuf, 0, test_dst, buf_size));
121 [ + + ]: 514 : for (int i = 0; i < buf_size / 4; i++) {
122 [ - + ]: 512 : REQUIRE_CMP(test_src[4 * i + 0], ==, test_dst[4 * i + 2], PRIu8);
123 [ - + ]: 512 : REQUIRE_CMP(test_src[4 * i + 1], ==, test_dst[4 * i + 3], PRIu8);
124 [ - + ]: 512 : REQUIRE_CMP(test_src[4 * i + 2], ==, test_dst[4 * i + 0], PRIu8);
125 [ - + ]: 512 : REQUIRE_CMP(test_src[4 * i + 3], ==, test_dst[4 * i + 1], PRIu8);
126 : : }
127 : 2 : pl_buf_destroy(gpu, &buf);
128 : 2 : pl_buf_destroy(gpu, &tbuf);
129 : : }
130 : :
131 : 5 : free(test_src);
132 : : }
133 : :
134 : 912 : static void test_cb(void *priv)
135 : : {
136 : : bool *flag = priv;
137 : 912 : *flag = true;
138 : 912 : }
139 : :
140 : 763 : static void pl_test_roundtrip(pl_gpu gpu, pl_tex tex[2],
141 : : uint8_t *src, uint8_t *dst)
142 : : {
143 [ + + - + ]: 763 : if (!tex[0] || !tex[1]) {
144 : : printf("failed creating test textures... skipping this test\n");
145 : 79 : return;
146 : : }
147 : :
148 : 684 : int texels = tex[0]->params.w;
149 [ + + ]: 684 : texels *= tex[0]->params.h ? tex[0]->params.h : 1;
150 [ + + ]: 684 : texels *= tex[0]->params.d ? tex[0]->params.d : 1;
151 : :
152 : 684 : pl_fmt fmt = tex[0]->params.format;
153 : 684 : size_t bytes = texels * fmt->texel_size;
154 : : memset(src, 0, bytes);
155 : : memset(dst, 0, bytes);
156 : :
157 [ + + ]: 978588 : for (int i = 0; i < texels; i++) {
158 : 977904 : uint8_t *data = &src[i * fmt->texel_size];
159 [ + + ]: 977904 : if (fmt->type == PL_FMT_FLOAT) {
160 [ + + ]: 902368 : for (int n = 0; n < fmt->num_components; n++) {
161 [ + + + - ]: 643888 : switch (fmt->component_depth[n]) {
162 : 381808 : case 16: *(uint16_t *) data = RANDOM_F16; data += 2; break;
163 : 218400 : case 32: *(float *) data = RANDOM_F32; data += 4; break;
164 : 43680 : case 64: *(double *) data = RANDOM_F64; data += 8; break;
165 : : }
166 : : }
167 : : } else {
168 [ + + ]: 5176800 : for (int n = 0; n < fmt->texel_size; n++)
169 : 4457376 : data[n] = RANDOM_U8;
170 : : }
171 : : }
172 : :
173 : : pl_timer ul, dl;
174 : 684 : ul = pl_timer_create(gpu);
175 : 684 : dl = pl_timer_create(gpu);
176 : :
177 : 684 : bool ran_ul = false, ran_dl = false;
178 : :
179 [ + + - + ]: 912 : REQUIRE(pl_tex_upload(gpu, &(struct pl_tex_transfer_params){
180 : : .tex = tex[0],
181 : : .ptr = src,
182 : : .timer = ul,
183 : : .callback = gpu->limits.callbacks ? test_cb : NULL,
184 : : .priv = &ran_ul,
185 : : }));
186 : :
187 : : // Test blitting, if possible for this format
188 : 684 : pl_tex dst_tex = tex[0];
189 [ + + + - ]: 684 : if (tex[0]->params.blit_src && tex[1]->params.blit_dst) {
190 : 252 : pl_tex_clear_ex(gpu, tex[1], (union pl_clear_color){0}); // for testing
191 : 252 : pl_tex_blit(gpu, &(struct pl_tex_blit_params) {
192 : 252 : .src = tex[0],
193 : : .dst = tex[1],
194 : : });
195 : 252 : dst_tex = tex[1];
196 : : }
197 : :
198 [ + + - + ]: 912 : REQUIRE(pl_tex_download(gpu, &(struct pl_tex_transfer_params){
199 : : .tex = dst_tex,
200 : : .ptr = dst,
201 : : .timer = dl,
202 : : .callback = gpu->limits.callbacks ? test_cb : NULL,
203 : : .priv = &ran_dl,
204 : : }));
205 : :
206 : 684 : pl_gpu_finish(gpu);
207 [ + + ]: 684 : if (gpu->limits.callbacks)
208 [ + - - + ]: 456 : REQUIRE(ran_ul && ran_dl);
209 : :
210 [ + + + + ]: 684 : if (fmt->emulated && fmt->type == PL_FMT_FLOAT) {
211 : : // TODO: can't memcmp here because bits might be lost due to the
212 : : // emulated 16/32 bit upload paths, figure out a better way to
213 : : // generate data and verify the roundtrip!
214 : : } else {
215 : 621 : REQUIRE_MEMEQ(src, dst, bytes);
216 : : }
217 : :
218 : : // Report timer results
219 : 684 : printf("upload time: %"PRIu64", download time: %"PRIu64"\n",
220 : : pl_timer_query(gpu, ul), pl_timer_query(gpu, dl));
221 : :
222 : 684 : pl_timer_destroy(gpu, &ul);
223 : 684 : pl_timer_destroy(gpu, &dl);
224 : : }
225 : :
226 : 6 : void pl_texture_tests(pl_gpu gpu)
227 : : {
228 : : const size_t max_size = 16*16*16 * 4 *sizeof(double);
229 : 6 : uint8_t *test_src = malloc(max_size * 2);
230 : 6 : uint8_t *test_dst = test_src + max_size;
231 : : printf("pl_texture_tests:\n");
232 : :
233 [ + + ]: 300 : for (int f = 0; f < gpu->num_formats; f++) {
234 : 294 : pl_fmt fmt = gpu->formats[f];
235 [ + + + + ]: 294 : if (fmt->opaque || !(fmt->caps & PL_FMT_CAP_HOST_READABLE))
236 : 33 : continue;
237 : :
238 : 261 : printf("- testing texture roundtrip for format %s\n", fmt->name);
239 [ - + ]: 261 : assert(fmt->texel_size <= 4 * sizeof(double));
240 : :
241 : 261 : struct pl_tex_params ref_params = {
242 : : .format = fmt,
243 : 261 : .blit_src = (fmt->caps & PL_FMT_CAP_BLITTABLE),
244 : : .blit_dst = (fmt->caps & PL_FMT_CAP_BLITTABLE),
245 : : .host_writable = true,
246 : : .host_readable = true,
247 : : .debug_tag = PL_DEBUG_TAG,
248 : : };
249 : :
250 : : pl_tex tex[2];
251 : :
252 [ + + ]: 261 : if (gpu->limits.max_tex_1d_dim >= 16) {
253 : : printf("-- 1D\n");
254 : 241 : struct pl_tex_params params = ref_params;
255 : 241 : params.w = 16;
256 [ + + ]: 241 : if (!gpu->limits.blittable_1d_3d)
257 : 163 : params.blit_src = params.blit_dst = false;
258 [ + + ]: 723 : for (int i = 0; i < PL_ARRAY_SIZE(tex); i++)
259 : 482 : tex[i] = pl_tex_create(gpu, ¶ms);
260 : 241 : pl_test_roundtrip(gpu, tex, test_src, test_dst);
261 [ + + ]: 723 : for (int i = 0; i < PL_ARRAY_SIZE(tex); i++)
262 : 482 : pl_tex_destroy(gpu, &tex[i]);
263 : : }
264 : :
265 [ + - ]: 261 : if (gpu->limits.max_tex_2d_dim >= 16) {
266 : : printf("-- 2D\n");
267 : 261 : struct pl_tex_params params = ref_params;
268 : 261 : params.w = params.h = 16;
269 [ + + ]: 783 : for (int i = 0; i < PL_ARRAY_SIZE(tex); i++)
270 : 522 : tex[i] = pl_tex_create(gpu, ¶ms);
271 : 261 : pl_test_roundtrip(gpu, tex, test_src, test_dst);
272 [ + + ]: 783 : for (int i = 0; i < PL_ARRAY_SIZE(tex); i++)
273 : 522 : pl_tex_destroy(gpu, &tex[i]);
274 : : }
275 : :
276 [ + - ]: 261 : if (gpu->limits.max_tex_3d_dim >= 16) {
277 : : printf("-- 3D\n");
278 : 261 : struct pl_tex_params params = ref_params;
279 : 261 : params.w = params.h = params.d = 16;
280 [ + + ]: 261 : if (!gpu->limits.blittable_1d_3d)
281 : 183 : params.blit_src = params.blit_dst = false;
282 [ + + ]: 783 : for (int i = 0; i < PL_ARRAY_SIZE(tex); i++)
283 : 522 : tex[i] = pl_tex_create(gpu, ¶ms);
284 : 261 : pl_test_roundtrip(gpu, tex, test_src, test_dst);
285 [ + + ]: 783 : for (int i = 0; i < PL_ARRAY_SIZE(tex); i++)
286 : 522 : pl_tex_destroy(gpu, &tex[i]);
287 : : }
288 : : }
289 : :
290 : 6 : free(test_src);
291 : 6 : }
292 : :
293 : 5 : static void pl_planar_tests(pl_gpu gpu)
294 : 5 : {
295 : 5 : pl_fmt fmt = pl_find_named_fmt(gpu, "g8_b8_r8_420");
296 [ + + ]: 5 : if (!fmt)
297 : 5 : return;
298 : : printf("pl_planar_tests:\n");
299 [ - + ]: 1 : REQUIRE_CMP(fmt->num_planes, ==, 3, "d");
300 : :
301 : : const int width = 64, height = 32;
302 : 1 : pl_tex tex = pl_tex_create(gpu, pl_tex_params(
303 : : .w = width,
304 : : .h = height,
305 : : .format = fmt,
306 : : .blit_dst = true,
307 : : .host_readable = true,
308 : : ));
309 [ - + ]: 1 : if (!tex)
310 : : return;
311 [ # # ]: 0 : for (int i = 0; i < fmt->num_planes; i++)
312 [ # # ]: 0 : REQUIRE(tex->planes[i]);
313 : :
314 : 0 : pl_tex plane = tex->planes[1];
315 : 0 : uint8_t data[(width * height) >> 2];
316 [ # # ]: 0 : REQUIRE_CMP(plane->params.w * plane->params.h, ==, PL_ARRAY_SIZE(data), "d");
317 : :
318 : 0 : pl_tex_clear(gpu, plane, (float[]){ (float) 0x80 / 0xFF, 0.0, 0.0, 1.0 });
319 [ # # ]: 0 : REQUIRE(pl_tex_download(gpu, pl_tex_transfer_params(
320 : : .tex = plane,
321 : : .ptr = data,
322 : : )));
323 : :
324 : 0 : uint8_t ref[PL_ARRAY_SIZE(data)];
325 : : memset(ref, 0x80, sizeof(ref));
326 : 0 : REQUIRE_MEMEQ(data, ref, PL_ARRAY_SIZE(data));
327 : :
328 : 0 : pl_tex_destroy(gpu, &tex);
329 : : }
330 : :
331 : 5 : static void pl_shader_tests(pl_gpu gpu)
332 : : {
333 [ + + ]: 5 : if (gpu->glsl.version < 410)
334 : 3 : return;
335 : : printf("pl_shader_tests:\n");
336 : :
337 : : const char *vert_shader =
338 : : "#version 410 \n"
339 : : "layout(location=0) in vec2 vertex_pos; \n"
340 : : "layout(location=1) in vec3 vertex_color; \n"
341 : : "layout(location=0) out vec3 frag_color; \n"
342 : : "void main() { \n"
343 : : " gl_Position = vec4(vertex_pos, 0, 1); \n"
344 : : " frag_color = vertex_color; \n"
345 : : "}";
346 : :
347 : : const char *frag_shader =
348 : : "#version 410 \n"
349 : : "layout(location=0) in vec3 frag_color; \n"
350 : : "layout(location=0) out vec4 out_color; \n"
351 : : "void main() { \n"
352 : : " out_color = vec4(frag_color, 1.0); \n"
353 : : "}";
354 : :
355 : : pl_fmt fbo_fmt;
356 : : enum pl_fmt_caps caps = PL_FMT_CAP_RENDERABLE | PL_FMT_CAP_BLITTABLE |
357 : : PL_FMT_CAP_LINEAR;
358 : :
359 : 2 : fbo_fmt = pl_find_fmt(gpu, PL_FMT_FLOAT, 4, 16, 32, caps);
360 [ + - ]: 2 : if (!fbo_fmt)
361 : : return;
362 : :
363 : : #define FBO_W 16
364 : : #define FBO_H 16
365 : :
366 : : pl_tex fbo;
367 : 4 : fbo = pl_tex_create(gpu, &(struct pl_tex_params) {
368 : : .format = fbo_fmt,
369 : : .w = FBO_W,
370 : : .h = FBO_H,
371 : : .renderable = true,
372 : 2 : .storable = !!(fbo_fmt->caps & PL_FMT_CAP_STORABLE),
373 : : .host_readable = true,
374 : : .blit_dst = true,
375 : : });
376 [ - + ]: 2 : REQUIRE(fbo);
377 : :
378 : 2 : pl_tex_clear_ex(gpu, fbo, (union pl_clear_color){0});
379 : :
380 : : pl_fmt vert_fmt;
381 : 2 : vert_fmt = pl_find_vertex_fmt(gpu, PL_FMT_FLOAT, 3);
382 [ - + ]: 2 : REQUIRE(vert_fmt);
383 : :
384 : : static const struct vertex { float pos[2]; float color[3]; } vertices[] = {
385 : : {{-1.0, -1.0}, {0, 0, 0}},
386 : : {{ 1.0, -1.0}, {1, 0, 0}},
387 : : {{-1.0, 1.0}, {0, 1, 0}},
388 : : {{ 1.0, 1.0}, {1, 1, 0}},
389 : : };
390 : :
391 : : pl_pass pass;
392 : 6 : pass = pl_pass_create(gpu, &(struct pl_pass_params) {
393 : : .type = PL_PASS_RASTER,
394 : : .target_format = fbo_fmt,
395 : : .vertex_shader = vert_shader,
396 : : .glsl_shader = frag_shader,
397 : :
398 : : .vertex_type = PL_PRIM_TRIANGLE_STRIP,
399 : : .vertex_stride = sizeof(struct vertex),
400 : : .num_vertex_attribs = 2,
401 : 2 : .vertex_attribs = (struct pl_vertex_attrib[]) {{
402 : : .name = "vertex_pos",
403 : 2 : .fmt = pl_find_vertex_fmt(gpu, PL_FMT_FLOAT, 2),
404 : : .location = 0,
405 : : .offset = offsetof(struct vertex, pos),
406 : : }, {
407 : : .name = "vertex_color",
408 : 2 : .fmt = pl_find_vertex_fmt(gpu, PL_FMT_FLOAT, 3),
409 : : .location = 1,
410 : : .offset = offsetof(struct vertex, color),
411 : : }},
412 : : });
413 [ - + ]: 2 : REQUIRE(pass);
414 [ + - - + ]: 2 : if (pass->params.cached_program || pass->params.cached_program_len) {
415 : : // Ensure both are set if either one is set
416 [ # # ]: 0 : REQUIRE(pass->params.cached_program);
417 [ # # ]: 0 : REQUIRE(pass->params.cached_program_len);
418 : : }
419 : :
420 : 2 : pl_timer timer = pl_timer_create(gpu);
421 : 2 : pl_pass_run(gpu, &(struct pl_pass_run_params) {
422 : : .pass = pass,
423 : : .target = fbo,
424 : : .vertex_count = PL_ARRAY_SIZE(vertices),
425 : : .vertex_data = vertices,
426 : : .timer = timer,
427 : : });
428 : :
429 : : // Wait until this pass is complete and report the timer result
430 : 2 : pl_gpu_finish(gpu);
431 : 2 : printf("timer query result: %"PRIu64"\n", pl_timer_query(gpu, timer));
432 : 2 : pl_timer_destroy(gpu, &timer);
433 : :
434 : : static float test_data[FBO_H * FBO_W * 4] = {0};
435 : :
436 : : // Test against the known pattern of `src`, only useful for roundtrip tests
437 : : #define TEST_FBO_PATTERN(eps, fmt, ...) \
438 : : do { \
439 : : printf("- testing pattern of " fmt "\n", __VA_ARGS__); \
440 : : REQUIRE(pl_tex_download(gpu, &(struct pl_tex_transfer_params) { \
441 : : .tex = fbo, \
442 : : .ptr = test_data, \
443 : : })); \
444 : : \
445 : : for (int y = 0; y < FBO_H; y++) { \
446 : : for (int x = 0; x < FBO_W; x++) { \
447 : : float *color = &test_data[(y * FBO_W + x) * 4]; \
448 : : REQUIRE_FEQ(color[0], (x + 0.5) / FBO_W, eps); \
449 : : REQUIRE_FEQ(color[1], (y + 0.5) / FBO_H, eps); \
450 : : REQUIRE_FEQ(color[2], 0.0, eps); \
451 : : REQUIRE_FEQ(color[3], 1.0, eps); \
452 : : } \
453 : : } \
454 : : } while (0)
455 : :
456 [ - + - + : 546 : TEST_FBO_PATTERN(1e-6, "%s", "initial rendering");
- + - + -
+ + + +
+ ]
457 : :
458 [ + - ]: 2 : if (sizeof(vertices) <= gpu->limits.max_vbo_size) {
459 : : // Test the use of an explicit vertex buffer
460 : 2 : pl_buf vert = pl_buf_create(gpu, &(struct pl_buf_params) {
461 : : .size = sizeof(vertices),
462 : : .initial_data = vertices,
463 : : .drawable = true,
464 : : });
465 : :
466 [ - + ]: 2 : REQUIRE(vert);
467 : 2 : pl_pass_run(gpu, &(struct pl_pass_run_params) {
468 : : .pass = pass,
469 : : .target = fbo,
470 : : .vertex_count = sizeof(vertices) / sizeof(struct vertex),
471 : : .vertex_buf = vert,
472 : : .buf_offset = 0,
473 : : });
474 : :
475 : 2 : pl_buf_destroy(gpu, &vert);
476 [ - + - + : 546 : TEST_FBO_PATTERN(1e-6, "%s", "using vertex buffer");
- + - + -
+ + + +
+ ]
477 : : }
478 : :
479 : : // Test the use of index buffers
480 : : static const uint16_t indices[] = { 3, 2, 1, 0 };
481 : 2 : pl_pass_run(gpu, &(struct pl_pass_run_params) {
482 : : .pass = pass,
483 : : .target = fbo,
484 : : .vertex_count = PL_ARRAY_SIZE(indices),
485 : : .vertex_data = vertices,
486 : : .index_data = indices,
487 : : });
488 : :
489 : 2 : pl_pass_destroy(gpu, &pass);
490 [ - + - + : 546 : TEST_FBO_PATTERN(1e-6, "%s", "using indexed rendering");
- + - + -
+ + + +
+ ]
491 : :
492 : : // Test the use of pl_dispatch
493 : 2 : pl_dispatch dp = pl_dispatch_create(gpu->log, gpu);
494 : 2 : pl_shader sh = pl_dispatch_begin(dp);
495 [ - + ]: 2 : REQUIRE(pl_shader_custom(sh, &(struct pl_custom_shader) {
496 : : .body = "color = vec4(col, 1.0);",
497 : : .input = PL_SHADER_SIG_NONE,
498 : : .output = PL_SHADER_SIG_COLOR,
499 : : }));
500 : :
501 [ - + ]: 2 : REQUIRE(pl_dispatch_vertex(dp, &(struct pl_dispatch_vertex_params) {
502 : : .shader = &sh,
503 : : .target = fbo,
504 : : .vertex_stride = sizeof(struct vertex),
505 : : .vertex_position_idx = 0,
506 : : .num_vertex_attribs = 2,
507 : : .vertex_attribs = (struct pl_vertex_attrib[]) {{
508 : : .name = "pos",
509 : : .fmt = pl_find_vertex_fmt(gpu, PL_FMT_FLOAT, 2),
510 : : .offset = offsetof(struct vertex, pos),
511 : : }, {
512 : : .name = "col",
513 : : .fmt = pl_find_vertex_fmt(gpu, PL_FMT_FLOAT, 3),
514 : : .offset = offsetof(struct vertex, color),
515 : : }},
516 : :
517 : : .vertex_type = PL_PRIM_TRIANGLE_STRIP,
518 : : .vertex_coords = PL_COORDS_NORMALIZED,
519 : : .vertex_count = PL_ARRAY_SIZE(vertices),
520 : : .vertex_data = vertices,
521 : : }));
522 : :
523 [ - + - + : 546 : TEST_FBO_PATTERN(1e-6, "%s", "using custom vertices");
- + - + -
+ + + +
+ ]
524 : :
525 : : static float src_data[FBO_H * FBO_W * 4] = {0};
526 : : memcpy(src_data, test_data, sizeof(src_data));
527 : :
528 : : pl_tex src;
529 : 4 : src = pl_tex_create(gpu, &(struct pl_tex_params) {
530 : : .format = fbo_fmt,
531 : : .w = FBO_W,
532 : : .h = FBO_H,
533 : 2 : .storable = fbo->params.storable,
534 : : .sampleable = true,
535 : : .initial_data = src_data,
536 : : });
537 : :
538 [ + - ]: 2 : if (fbo->params.storable) {
539 : : // Test 1x1 blit, to make sure the scaling code runs
540 [ - + ]: 2 : REQUIRE(pl_tex_blit_compute(gpu, &(struct pl_tex_blit_params) {
541 : : .src = src,
542 : : .dst = fbo,
543 : : .src_rc = {0, 0, 0, 1, 1, 1},
544 : : .dst_rc = {0, 0, 0, FBO_W, FBO_H, 1},
545 : : .sample_mode = PL_TEX_SAMPLE_NEAREST,
546 : : }));
547 : :
548 : : // Test non-resizing blit, which uses the efficient imageLoad path
549 [ - + ]: 2 : REQUIRE(pl_tex_blit_compute(gpu, &(struct pl_tex_blit_params) {
550 : : .src = src,
551 : : .dst = fbo,
552 : : .src_rc = {0, 0, 0, FBO_W, FBO_H, 1},
553 : : .dst_rc = {0, 0, 0, FBO_W, FBO_H, 1},
554 : : .sample_mode = PL_TEX_SAMPLE_NEAREST,
555 : : }));
556 : :
557 [ - + - + : 546 : TEST_FBO_PATTERN(1e-6, "%s", "pl_tex_blit_compute");
- + - + -
+ + + +
+ ]
558 : : }
559 : :
560 : : // Test encoding/decoding of all gamma functions, color spaces, etc.
561 [ + + ]: 36 : for (enum pl_color_transfer trc = 0; trc < PL_COLOR_TRC_COUNT; trc++) {
562 : 34 : struct pl_color_space test_csp = {
563 : : .transfer = trc,
564 : : .hdr.min_luma = PL_COLOR_HDR_BLACK,
565 : : };
566 : 34 : sh = pl_dispatch_begin(dp);
567 : 34 : pl_shader_sample_nearest(sh, pl_sample_src( .tex = src ));
568 : 34 : pl_shader_delinearize(sh, &test_csp);
569 : 34 : pl_shader_linearize(sh, &test_csp);
570 [ - + ]: 34 : REQUIRE(pl_dispatch_finish(dp, pl_dispatch_params(
571 : : .shader = &sh,
572 : : .target = fbo,
573 : : )));
574 : :
575 [ + + ]: 34 : float epsilon = pl_color_transfer_is_hdr(trc) ? 1e-4 : 1e-6;
576 [ - + - + : 9316 : TEST_FBO_PATTERN(epsilon, "transfer function: %s", pl_color_transfer_name(trc));
- + - + -
+ + + +
+ ]
577 : : }
578 : :
579 [ + + ]: 26 : for (enum pl_color_system sys = 0; sys < PL_COLOR_SYSTEM_COUNT; sys++) {
580 [ + + ]: 24 : if (sys == PL_COLOR_SYSTEM_DOLBYVISION)
581 : 6 : continue; // requires metadata
582 : 22 : sh = pl_dispatch_begin(dp);
583 : 22 : pl_shader_sample_nearest(sh, pl_sample_src( .tex = src ));
584 : 22 : pl_shader_encode_color(sh, &(struct pl_color_repr) { .sys = sys });
585 : 22 : pl_shader_decode_color(sh, &(struct pl_color_repr) { .sys = sys }, NULL);
586 [ - + ]: 22 : REQUIRE(pl_dispatch_finish(dp, &(struct pl_dispatch_params) {
587 : : .shader = &sh,
588 : : .target = fbo,
589 : : }));
590 : :
591 : : float epsilon;
592 [ + + + ]: 22 : switch (sys) {
593 : : case PL_COLOR_SYSTEM_BT_2020_C:
594 : : case PL_COLOR_SYSTEM_XYZ:
595 : : epsilon = 1e-5;
596 : : break;
597 : :
598 : 4 : case PL_COLOR_SYSTEM_BT_2100_PQ:
599 : : case PL_COLOR_SYSTEM_BT_2100_HLG:
600 : : // These seem to be horrifically noisy and prone to breaking on
601 : : // edge cases for some reason
602 : : // TODO: figure out why!
603 : 4 : continue;
604 : :
605 : 14 : default: epsilon = 1e-6; break;
606 : : }
607 : :
608 [ - + - + : 4932 : TEST_FBO_PATTERN(epsilon, "color system: %s", pl_color_system_name(sys));
- + - + -
+ + + +
+ ]
609 : : }
610 : :
611 : : // Repeat this a few times to test the caching
612 : 2 : pl_cache cache = pl_cache_create(pl_cache_params( .log = gpu->log ));
613 : 2 : pl_gpu_set_cache(gpu, cache);
614 [ + + ]: 22 : for (int i = 0; i < 10; i++) {
615 [ + + ]: 20 : if (i == 5) {
616 : : printf("Recreating pl_dispatch to test the caching\n");
617 : 2 : size_t size = pl_dispatch_save(dp, NULL);
618 [ - + ]: 2 : REQUIRE(size);
619 : 2 : uint8_t *cache_data = malloc(size);
620 [ - + ]: 2 : REQUIRE(cache_data);
621 [ - + ]: 2 : REQUIRE_CMP(pl_dispatch_save(dp, cache_data), ==, size, "zu");
622 : :
623 : 2 : pl_dispatch_destroy(&dp);
624 : 2 : dp = pl_dispatch_create(gpu->log, gpu);
625 : 2 : pl_dispatch_load(dp, cache_data);
626 : :
627 : : // Test to make sure the pass regenerates the same cache
628 : : uint64_t hash = pl_str_hash((pl_str) { cache_data, size });
629 [ - + ]: 2 : REQUIRE_CMP(pl_dispatch_save(dp, NULL), ==, size, "zu");
630 [ - + ]: 2 : REQUIRE_CMP(pl_dispatch_save(dp, cache_data), ==, size, "zu");
631 [ - + ]: 2 : REQUIRE_CMP(pl_str_hash((pl_str) { cache_data, size }), ==, hash, PRIu64);
632 : 2 : free(cache_data);
633 : : }
634 : :
635 : 20 : sh = pl_dispatch_begin(dp);
636 : :
637 : : // For testing, force the use of CS if possible
638 [ + - ]: 20 : if (gpu->glsl.compute) {
639 : 20 : sh->type = SH_COMPUTE;
640 : 20 : sh->group_size[0] = 8;
641 : 20 : sh->group_size[1] = 8;
642 : : }
643 : :
644 : 20 : pl_shader_deband(sh, pl_sample_src( .tex = src ), pl_deband_params(
645 : : .iterations = 0,
646 : : .grain = 0.0,
647 : : ));
648 : :
649 [ - + ]: 20 : REQUIRE(pl_dispatch_finish(dp, &(struct pl_dispatch_params) {
650 : : .shader = &sh,
651 : : .target = fbo,
652 : : }));
653 [ - + - + : 5460 : TEST_FBO_PATTERN(1e-6, "deband iter %d", i);
- + - + -
+ + + +
+ ]
654 : : }
655 : :
656 : 2 : pl_gpu_set_cache(gpu, NULL);
657 : 2 : pl_cache_destroy(&cache);
658 : :
659 : : // Test peak detection and readback if possible
660 : 2 : sh = pl_dispatch_begin(dp);
661 : 2 : pl_shader_sample_nearest(sh, pl_sample_src( .tex = src ));
662 : :
663 : 2 : pl_shader_obj peak_state = NULL;
664 : 2 : struct pl_color_space csp_gamma22 = { .transfer = PL_COLOR_TRC_GAMMA22 };
665 : 2 : struct pl_peak_detect_params peak_params = { .minimum_peak = 0.01 };
666 [ + - ]: 2 : if (pl_shader_detect_peak(sh, csp_gamma22, &peak_state, &peak_params)) {
667 [ - + ]: 2 : REQUIRE(pl_dispatch_compute(dp, &(struct pl_dispatch_compute_params) {
668 : : .shader = &sh,
669 : : .width = fbo->params.w,
670 : : .height = fbo->params.h,
671 : : }));
672 : :
673 : : struct pl_hdr_metadata hdr;
674 [ - + ]: 2 : REQUIRE(pl_get_detected_hdr_metadata(peak_state, &hdr));
675 : :
676 : : float real_peak = 0, real_avg = 0;
677 [ + + ]: 34 : for (int y = 0; y < FBO_H; y++) {
678 [ + + ]: 544 : for (int x = 0; x < FBO_W; x++) {
679 : 512 : float *color = &src_data[(y * FBO_W + x) * 4];
680 : 512 : float luma = 0.212639f * powf(color[0], 2.2f) +
681 : 512 : 0.715169f * powf(color[1], 2.2f) +
682 : 512 : 0.072192f * powf(color[2], 2.2f);
683 : 512 : luma = pl_hdr_rescale(PL_HDR_NORM, PL_HDR_PQ, luma);
684 [ + + ]: 512 : real_peak = PL_MAX(real_peak, luma);
685 : 512 : real_avg += luma;
686 : : }
687 : : }
688 : 2 : real_avg = real_avg / (FBO_W * FBO_H);
689 [ - + ]: 2 : REQUIRE_FEQ(hdr.max_pq_y, real_peak, 1e-4);
690 [ - + ]: 2 : REQUIRE_FEQ(hdr.avg_pq_y, real_avg, 1e-3);
691 : : }
692 : :
693 : 2 : pl_dispatch_abort(dp, &sh);
694 : 2 : pl_shader_obj_destroy(&peak_state);
695 : :
696 : : // Test film grain synthesis
697 : 2 : pl_shader_obj grain = NULL;
698 : 2 : struct pl_film_grain_params grain_params = {
699 : : .tex = src,
700 : : .components = 3,
701 : : .component_mapping = { 0, 1, 2},
702 : 2 : .repr = &(struct pl_color_repr) {
703 : : .sys = PL_COLOR_SYSTEM_BT_709,
704 : : .levels = PL_COLOR_LEVELS_LIMITED,
705 : : .bits = { .color_depth = 10, .sample_depth = 10 },
706 : : },
707 : : };
708 : :
709 [ + + ]: 6 : for (int i = 0; i < 2; i++) {
710 : 4 : grain_params.data.type = PL_FILM_GRAIN_AV1;
711 : 4 : grain_params.data.params.av1 = av1_grain_data;
712 : 4 : grain_params.data.params.av1.overlap = !!i;
713 : 4 : grain_params.data.seed = rand();
714 : :
715 : 4 : sh = pl_dispatch_begin(dp);
716 : 4 : pl_shader_film_grain(sh, &grain, &grain_params);
717 [ - + ]: 4 : REQUIRE(pl_dispatch_finish(dp, &(struct pl_dispatch_params) {
718 : : .shader = &sh,
719 : : .target = fbo,
720 : : }));
721 : : }
722 : :
723 [ + - ]: 2 : if (gpu->glsl.compute) {
724 : 2 : grain_params.data.type = PL_FILM_GRAIN_H274;
725 : 2 : grain_params.data.params.h274 = h274_grain_data;
726 : 2 : grain_params.data.seed = rand();
727 : :
728 : 2 : sh = pl_dispatch_begin(dp);
729 : 2 : pl_shader_film_grain(sh, &grain, &grain_params);
730 [ - + ]: 2 : REQUIRE(pl_dispatch_finish(dp, &(struct pl_dispatch_params) {
731 : : .shader = &sh,
732 : : .target = fbo,
733 : : }));
734 : : }
735 : 2 : pl_shader_obj_destroy(&grain);
736 : :
737 : : // Test custom shaders
738 : 4 : struct pl_custom_shader custom = {
739 : : .header =
740 : : "vec3 invert(vec3 color) \n"
741 : : "{ \n"
742 : : " return vec3(1.0) - color; \n"
743 : : "} \n",
744 : :
745 : : .body =
746 : : "color = vec4(gl_FragCoord.xy, 0.0, 1.0); \n"
747 : : "color.rgb = invert(color.rgb) + offset; \n",
748 : :
749 : : .input = PL_SHADER_SIG_NONE,
750 : : .output = PL_SHADER_SIG_COLOR,
751 : :
752 : : .num_variables = 1,
753 : 4 : .variables = &(struct pl_shader_var) {
754 : 2 : .var = pl_var_float("offset"),
755 : 2 : .data = &(float) { 0.1 },
756 : : },
757 : : };
758 : :
759 : 2 : sh = pl_dispatch_begin(dp);
760 [ - + ]: 2 : REQUIRE(pl_shader_custom(sh, &custom));
761 [ - + ]: 2 : REQUIRE(pl_dispatch_finish(dp, &(struct pl_dispatch_params) {
762 : : .shader = &sh,
763 : : .target = fbo,
764 : : }));
765 : :
766 : : // Test dolbyvision
767 : 2 : struct pl_color_repr repr = {
768 : : .sys = PL_COLOR_SYSTEM_DOLBYVISION,
769 : : .dovi = &dovi_meta,
770 : : };
771 : :
772 : 2 : sh = pl_dispatch_begin(dp);
773 : 2 : pl_shader_sample_direct(sh, pl_sample_src( .tex = src ));
774 : 2 : pl_shader_decode_color(sh, &repr, NULL);
775 [ - + ]: 2 : REQUIRE(pl_dispatch_finish(dp, &(struct pl_dispatch_params) {
776 : : .shader = &sh,
777 : : .target = fbo,
778 : : }));
779 : :
780 : : // Test deinterlacing
781 : 2 : sh = pl_dispatch_begin(dp);
782 : 2 : pl_shader_deinterlace(sh, pl_deinterlace_source( .cur = pl_field_pair(src) ), NULL);
783 [ - + ]: 2 : REQUIRE(pl_dispatch_finish(dp, pl_dispatch_params(
784 : : .shader = &sh,
785 : : .target = fbo,
786 : : )));
787 : :
788 : : // Test error diffusion
789 [ + - ]: 2 : if (fbo->params.storable) {
790 [ + + ]: 22 : for (int i = 0; i < pl_num_error_diffusion_kernels; i++) {
791 : 20 : const struct pl_error_diffusion_kernel *k = pl_error_diffusion_kernels[i];
792 : 20 : printf("- testing error diffusion kernel '%s'\n", k->name);
793 : 20 : sh = pl_dispatch_begin(dp);
794 : 20 : bool ok = pl_shader_error_diffusion(sh, pl_error_diffusion_params(
795 : : .input_tex = src,
796 : : .output_tex = fbo,
797 : : .new_depth = 8,
798 : : .kernel = k,
799 : : ));
800 : :
801 [ - + ]: 20 : if (!ok) {
802 : 0 : fprintf(stderr, "kernel '%s' exceeds GPU limits, skipping...\n", k->name);
803 : 0 : continue;
804 : : }
805 : :
806 [ - + ]: 20 : REQUIRE(pl_dispatch_compute(dp, pl_dispatch_compute_params(
807 : : .shader = &sh,
808 : : .dispatch_size = {1, 1, 1},
809 : : )));
810 : : }
811 : : }
812 : :
813 : 2 : pl_dispatch_destroy(&dp);
814 : 2 : pl_tex_destroy(gpu, &src);
815 : 2 : pl_tex_destroy(gpu, &fbo);
816 : : }
817 : :
818 : 5 : static void pl_scaler_tests(pl_gpu gpu)
819 : : {
820 : 5 : pl_fmt src_fmt = pl_find_fmt(gpu, PL_FMT_FLOAT, 1, 16, 32, PL_FMT_CAP_LINEAR);
821 : 5 : pl_fmt fbo_fmt = pl_find_fmt(gpu, PL_FMT_FLOAT, 1, 16, 32, PL_FMT_CAP_RENDERABLE);
822 [ - + ]: 5 : if (!src_fmt || !fbo_fmt)
823 : 0 : return;
824 : : printf("pl_scaler_tests:\n");
825 : :
826 : : float *fbo_data = NULL;
827 : 5 : pl_shader_obj lut = NULL;
828 : :
829 : : static float data_5x5[5][5] = {
830 : : { 0, 0, 0, 0, 0 },
831 : : { 0, 0, 0, 0, 0 },
832 : : { 0, 0, 1, 0, 0 },
833 : : { 0, 0, 0, 0, 0 },
834 : : { 0, 0, 0, 0, 0 },
835 : : };
836 : :
837 : 5 : pl_tex dot5x5 = pl_tex_create(gpu, &(struct pl_tex_params) {
838 : : .w = 5,
839 : : .h = 5,
840 : : .format = src_fmt,
841 : : .sampleable = true,
842 : : .initial_data = &data_5x5[0][0],
843 : : });
844 : :
845 : 5 : struct pl_tex_params fbo_params = {
846 : : .w = 100,
847 : : .h = 100,
848 : : .format = fbo_fmt,
849 : : .renderable = true,
850 : 5 : .storable = fbo_fmt->caps & PL_FMT_CAP_STORABLE,
851 : 5 : .host_readable = fbo_fmt->caps & PL_FMT_CAP_HOST_READABLE,
852 : : };
853 : :
854 : 5 : pl_tex fbo = pl_tex_create(gpu, &fbo_params);
855 : 5 : pl_dispatch dp = pl_dispatch_create(gpu->log, gpu);
856 [ + + - + ]: 5 : if (!dot5x5 || !fbo || !dp)
857 : 1 : goto error;
858 : :
859 : 4 : pl_shader sh = pl_dispatch_begin(dp);
860 [ - + ]: 4 : REQUIRE(pl_shader_sample_polar(sh,
861 : : pl_sample_src(
862 : : .tex = dot5x5,
863 : : .new_w = fbo->params.w,
864 : : .new_h = fbo->params.h,
865 : : ),
866 : : pl_sample_filter_params(
867 : : .filter = pl_filter_ewa_lanczos,
868 : : .lut = &lut,
869 : : .no_compute = !fbo->params.storable,
870 : : )
871 : : ));
872 [ - + ]: 4 : REQUIRE(pl_dispatch_finish(dp, &(struct pl_dispatch_params) {
873 : : .shader = &sh,
874 : : .target = fbo,
875 : : }));
876 : :
877 [ - + ]: 4 : if (fbo->params.host_readable) {
878 : 4 : fbo_data = malloc(fbo->params.w * fbo->params.h * sizeof(float));
879 [ - + ]: 4 : REQUIRE(pl_tex_download(gpu, &(struct pl_tex_transfer_params) {
880 : : .tex = fbo,
881 : : .ptr = fbo_data,
882 : : }));
883 : :
884 : : #ifdef PRINT_OUTPUT
885 : : int max = 255;
886 : : printf("P2\n%d %d\n%d\n", fbo->params.w, fbo->params.h, max);
887 : : for (int y = 0; y < fbo->params.h; y++) {
888 : : for (int x = 0; x < fbo->params.w; x++) {
889 : : float v = fbo_data[y * fbo->params.h + x];
890 : : printf("%d ", (int) round(fmin(fmax(v, 0.0), 1.0) * max));
891 : : }
892 : : printf("\n");
893 : : }
894 : : #endif
895 : : }
896 : :
897 : 0 : error:
898 : 5 : free(fbo_data);
899 : 5 : pl_shader_obj_destroy(&lut);
900 : 5 : pl_dispatch_destroy(&dp);
901 : 5 : pl_tex_destroy(gpu, &dot5x5);
902 : 5 : pl_tex_destroy(gpu, &fbo);
903 : : }
904 : :
905 : : static const char *user_shader_tests[] = {
906 : : // Test hooking, saving and loading
907 : : "// Example of a comment at the beginning \n"
908 : : " \n"
909 : : "//!HOOK NATIVE \n"
910 : : "//!DESC upscale image \n"
911 : : "//!BIND HOOKED \n"
912 : : "//!WIDTH HOOKED.w 10 * \n"
913 : : "//!HEIGHT HOOKED.h 10 * \n"
914 : : "//!SAVE NATIVEBIG \n"
915 : : "//!WHEN NATIVE.w 500 < \n"
916 : : " \n"
917 : : "vec4 hook() \n"
918 : : "{ \n"
919 : : " return HOOKED_texOff(0); \n"
920 : : "} \n"
921 : : " \n"
922 : : "//!HOOK MAIN \n"
923 : : "//!DESC downscale bigger image \n"
924 : : "//!WHEN NATIVE.w 500 < \n"
925 : : "//!BIND NATIVEBIG \n"
926 : : " \n"
927 : : "vec4 hook() \n"
928 : : "{ \n"
929 : : " return NATIVEBIG_texOff(0); \n"
930 : : "} \n",
931 : :
932 : : // Test use of textures
933 : : "//!HOOK MAIN \n"
934 : : "//!DESC turn everything into colorful pixels \n"
935 : : "//!BIND HOOKED \n"
936 : : "//!BIND DISCO \n"
937 : : "//!COMPONENTS 3 \n"
938 : : " \n"
939 : : "vec4 hook() \n"
940 : : "{ \n"
941 : : " return vec4(DISCO_tex(HOOKED_pos * 10.0).rgb, 1); \n"
942 : : "} \n"
943 : : " \n"
944 : : "//!TEXTURE DISCO \n"
945 : : "//!SIZE 3 3 \n"
946 : : "//!FORMAT rgba8 \n"
947 : : "//!FILTER NEAREST \n"
948 : : "//!BORDER REPEAT \n"
949 : : "ff0000ff00ff00ff0000ffff00ffffffff00ffffffff00ff4c4c4cff999999ffffffffff\n"
950 : :
951 : : // Test custom parameters
952 : : "//!PARAM test \n"
953 : : "//!DESC test parameter \n"
954 : : "//!TYPE DYNAMIC float \n"
955 : : "//!MINIMUM 0.0 \n"
956 : : "//!MAXIMUM 100.0 \n"
957 : : "1.0 \n"
958 : : " \n"
959 : : "//!PARAM testconst \n"
960 : : "//!TYPE CONSTANT uint \n"
961 : : "//!MAXIMUM 16 \n"
962 : : "3 \n"
963 : : " \n"
964 : : "//!PARAM testdefine \n"
965 : : "//!TYPE DEFINE \n"
966 : : "100 \n"
967 : : " \n"
968 : : "//!PARAM testenum \n"
969 : : "//!TYPE ENUM DEFINE \n"
970 : : "FOO \n"
971 : : "BAR \n"
972 : : " \n"
973 : : "//!HOOK MAIN \n"
974 : : "//!WHEN testconst 30 > \n"
975 : : "#error should not be run \n"
976 : : " \n"
977 : : "//!HOOK MAIN \n"
978 : : "//!WHEN testenum FOO = \n"
979 : : "#if testenum == BAR \n"
980 : : " #error bad \n"
981 : : "#endif \n"
982 : : "vec4 hook() { return vec4(0.0); } \n"
983 : : };
984 : :
985 : : static const char *compute_shader_tests[] = {
986 : : // Test use of storage/buffer resources
987 : : "//!HOOK MAIN \n"
988 : : "//!DESC attach some storage objects \n"
989 : : "//!BIND tex_storage \n"
990 : : "//!BIND buf_uniform \n"
991 : : "//!BIND buf_storage \n"
992 : : "//!COMPONENTS 4 \n"
993 : : " \n"
994 : : "vec4 hook() \n"
995 : : "{ \n"
996 : : " return vec4(foo, bar, bat); \n"
997 : : "} \n"
998 : : " \n"
999 : : "//!TEXTURE tex_storage \n"
1000 : : "//!SIZE 100 100 \n"
1001 : : "//!FORMAT r32f \n"
1002 : : "//!STORAGE \n"
1003 : : " \n"
1004 : : "//!BUFFER buf_uniform \n"
1005 : : "//!VAR float foo \n"
1006 : : "//!VAR float bar \n"
1007 : : "0000000000000000 \n"
1008 : : " \n"
1009 : : "//!BUFFER buf_storage \n"
1010 : : "//!VAR vec2 bat \n"
1011 : : "//!VAR int big[32]; \n"
1012 : : "//!STORAGE \n",
1013 : :
1014 : : };
1015 : :
1016 : : static const char *test_luts[] = {
1017 : :
1018 : : "TITLE \"1D identity\" \n"
1019 : : "LUT_1D_SIZE 2 \n"
1020 : : "0.0 0.0 0.0 \n"
1021 : : "1.0 1.0 1.0 \n",
1022 : :
1023 : : "TITLE \"3D identity\" \n"
1024 : : "LUT_3D_SIZE 2 \n"
1025 : : "0.0 0.0 0.0 \n"
1026 : : "1.0 0.0 0.0 \n"
1027 : : "0.0 1.0 0.0 \n"
1028 : : "1.0 1.0 0.0 \n"
1029 : : "0.0 0.0 1.0 \n"
1030 : : "1.0 0.0 1.0 \n"
1031 : : "0.0 1.0 1.0 \n"
1032 : : "1.0 1.0 1.0 \n"
1033 : :
1034 : : };
1035 : :
1036 : 240 : static bool frame_passthrough(pl_gpu gpu, pl_tex *tex,
1037 : : const struct pl_source_frame *src, struct pl_frame *out_frame)
1038 : : {
1039 : 240 : const struct pl_frame *frame = src->frame_data;
1040 : 240 : *out_frame = *frame;
1041 : 240 : return true;
1042 : : }
1043 : :
1044 : 88 : static enum pl_queue_status get_frame_ptr(struct pl_source_frame *out_frame,
1045 : : const struct pl_queue_params *qparams)
1046 : : {
1047 : 88 : const struct pl_source_frame **pframe = qparams->priv;
1048 [ + + ]: 88 : if (!(*pframe)->frame_data)
1049 : : return PL_QUEUE_EOF;
1050 : :
1051 : 80 : *out_frame = *(*pframe)++;
1052 : 80 : return PL_QUEUE_OK;
1053 : : }
1054 : :
1055 : 928 : static void render_info_cb(void *priv, const struct pl_render_info *info)
1056 : : {
1057 : 928 : printf("{%d} Executed shader: %s\n", info->index,
1058 : 928 : info->pass->shader->description);
1059 : 928 : }
1060 : :
1061 : 5 : static void pl_render_tests(pl_gpu gpu)
1062 : : {
1063 : 5 : pl_tex img_tex = NULL, fbo = NULL;
1064 : 5 : pl_renderer rr = NULL;
1065 : : printf("pl_render_tests:\n");
1066 : :
1067 : : enum { width = 50, height = 50 };
1068 : : static float data[width][height];
1069 [ + + ]: 255 : for (int y = 0; y < height; y++) {
1070 [ + + ]: 12750 : for (int x = 0; x < width; x++)
1071 : 12500 : data[y][x] = RANDOM;
1072 : : }
1073 : :
1074 : 5 : struct pl_plane img_plane = {0};
1075 : 5 : struct pl_plane_data plane_data = {
1076 : : .type = PL_FMT_FLOAT,
1077 : : .width = width,
1078 : : .height = height,
1079 : : .component_size = { 8 * sizeof(float) },
1080 : : .component_map = { 0 },
1081 : : .pixel_stride = sizeof(float),
1082 : : .pixels = data,
1083 : : };
1084 : :
1085 [ + + ]: 5 : if (!pl_recreate_plane(gpu, NULL, &fbo, &plane_data))
1086 : 1 : return;
1087 : :
1088 [ - + ]: 4 : if (!pl_upload_plane(gpu, &img_plane, &img_tex, &plane_data))
1089 : 0 : goto error;
1090 : :
1091 : 4 : rr = pl_renderer_create(gpu->log, gpu);
1092 : 4 : pl_tex_clear_ex(gpu, fbo, (union pl_clear_color){0});
1093 : :
1094 : 4 : struct pl_frame image = {
1095 : : .num_planes = 1,
1096 : : .planes = { img_plane },
1097 : : .repr = {
1098 : : .sys = PL_COLOR_SYSTEM_BT_709,
1099 : : .levels = PL_COLOR_LEVELS_FULL,
1100 : : },
1101 : : .color = pl_color_space_srgb,
1102 : : };
1103 : :
1104 : 4 : struct pl_frame target = {
1105 : : .num_planes = 1,
1106 : : .planes = {{
1107 : : .texture = fbo,
1108 : : .components = 3,
1109 : : .component_mapping = {0, 1, 2},
1110 : : }},
1111 : : .repr = {
1112 : : .sys = PL_COLOR_SYSTEM_RGB,
1113 : : .levels = PL_COLOR_LEVELS_FULL,
1114 : : .bits.color_depth = 32,
1115 : : },
1116 : : .color = pl_color_space_srgb,
1117 : : };
1118 : :
1119 [ - + ]: 4 : REQUIRE(pl_render_image(rr, &image, &target, NULL));
1120 [ - + ]: 4 : REQUIRE(pl_renderer_get_errors(rr).errors == PL_RENDER_ERR_NONE);
1121 : :
1122 : : // TODO: embed a reference texture and ensure it matches
1123 : :
1124 : : // Test a bunch of different params
1125 : : #define TEST(SNAME, STYPE, DEFAULT, FIELD, LIMIT) \
1126 : : do { \
1127 : : for (int i = 0; i <= LIMIT; i++) { \
1128 : : printf("- testing `" #STYPE "." #FIELD " = %d`\n", i); \
1129 : : struct pl_render_params params = pl_render_default_params; \
1130 : : params.force_dither = true; \
1131 : : struct STYPE tmp = DEFAULT; \
1132 : : tmp.FIELD = i; \
1133 : : params.SNAME = &tmp; \
1134 : : REQUIRE(pl_render_image(rr, &image, &target, ¶ms)); \
1135 : : pl_gpu_flush(gpu); \
1136 : : REQUIRE(pl_renderer_get_errors(rr).errors == PL_RENDER_ERR_NONE); \
1137 : : } \
1138 : : } while (0)
1139 : :
1140 : : #define TEST_PARAMS(NAME, FIELD, LIMIT) \
1141 : : TEST(NAME##_params, pl_##NAME##_params, pl_##NAME##_default_params, FIELD, LIMIT)
1142 : :
1143 : 4 : image.crop.x1 = width / 2.0;
1144 : 4 : image.crop.y1 = height / 2.0;
1145 [ + + ]: 112 : for (int i = 0; i < pl_num_scale_filters; i++) {
1146 : 108 : struct pl_render_params params = pl_render_default_params;
1147 : 108 : params.upscaler = pl_scale_filters[i].filter;
1148 : 108 : printf("- testing `params.upscaler = /* %s */`\n", pl_scale_filters[i].name);
1149 [ - + ]: 108 : REQUIRE(pl_render_image(rr, &image, &target, ¶ms));
1150 : 108 : pl_gpu_flush(gpu);
1151 [ - + ]: 108 : REQUIRE(pl_renderer_get_errors(rr).errors == PL_RENDER_ERR_NONE);
1152 : : }
1153 : 4 : image.crop.x1 = image.crop.y1 = 0;
1154 : :
1155 : 4 : target.crop.x1 = width / 2.0;
1156 : 4 : target.crop.y1 = height / 2.0;
1157 [ + + ]: 112 : for (int i = 0; i < pl_num_scale_filters; i++) {
1158 : 108 : struct pl_render_params params = pl_render_default_params;
1159 : 108 : params.downscaler = pl_scale_filters[i].filter;
1160 : 108 : printf("- testing `params.downscaler = /* %s */`\n", pl_scale_filters[i].name);
1161 [ - + ]: 108 : REQUIRE(pl_render_image(rr, &image, &target, ¶ms));
1162 : 108 : pl_gpu_flush(gpu);
1163 [ - + ]: 108 : REQUIRE(pl_renderer_get_errors(rr).errors == PL_RENDER_ERR_NONE);
1164 : : }
1165 : 4 : target.crop.x1 = target.crop.y1 = 0;
1166 : :
1167 [ - + - + : 20 : TEST_PARAMS(deband, iterations, 3);
+ + ]
1168 [ - + - + : 12 : TEST_PARAMS(sigmoid, center, 1);
+ + ]
1169 [ - + - + : 20 : TEST_PARAMS(color_map, intent, PL_INTENT_ABSOLUTE_COLORIMETRIC);
+ + ]
1170 [ - + - + : 20 : TEST_PARAMS(dither, method, PL_DITHER_WHITE_NOISE);
+ + ]
1171 [ - + - + : 12 : TEST_PARAMS(dither, temporal, true);
+ + ]
1172 [ - + - + : 12 : TEST_PARAMS(distort, alpha_mode, PL_ALPHA_INDEPENDENT);
+ + ]
1173 [ - + - + : 12 : TEST_PARAMS(distort, constrain, true);
+ + ]
1174 [ - + - + : 12 : TEST_PARAMS(distort, bicubic, true);
+ + ]
1175 [ - + - + : 12 : TEST(cone_params, pl_cone_params, pl_vision_deuteranomaly, strength, 0);
+ + ]
1176 : :
1177 : : // Test gamma-correct dithering
1178 : 4 : target.repr.bits.color_depth = 2;
1179 [ - + - + : 32 : TEST_PARAMS(dither, transfer, PL_COLOR_TRC_GAMMA22);
+ + ]
1180 : 4 : target.repr.bits.color_depth = 32;
1181 : :
1182 : : // Test HDR tone mapping
1183 : 4 : image.color = pl_color_space_hdr10;
1184 [ - + - + : 12 : TEST_PARAMS(color_map, visualize_lut, true);
+ + ]
1185 [ + + ]: 4 : if (gpu->limits.max_ssbo_size)
1186 [ - + - + : 9 : TEST_PARAMS(peak_detect, allow_delayed, true);
+ + ]
1187 : :
1188 : : // Test inverse tone-mapping and pure BPC
1189 : 4 : image.color.hdr.max_luma = 1000;
1190 : 4 : target.color.hdr.max_luma = 4000;
1191 : 4 : target.color.hdr.min_luma = 0.02;
1192 [ - + - + : 12 : TEST_PARAMS(color_map, inverse_tone_mapping, true);
+ + ]
1193 : :
1194 : 4 : image.color = pl_color_space_srgb;
1195 : 4 : target.color = pl_color_space_srgb;
1196 : :
1197 : : // Test some misc stuff
1198 : 4 : struct pl_render_params params = pl_render_default_params;
1199 : 4 : params.color_adjustment = &(struct pl_color_adjustment) {
1200 : : .brightness = 0.1,
1201 : : .contrast = 0.9,
1202 : : .saturation = 1.5,
1203 : : .gamma = 0.8,
1204 : : .temperature = 0.3,
1205 : : };
1206 [ - + ]: 4 : REQUIRE(pl_render_image(rr, &image, &target, ¶ms));
1207 [ - + ]: 4 : REQUIRE(pl_renderer_get_errors(rr).errors == PL_RENDER_ERR_NONE);
1208 : 4 : params = pl_render_default_params;
1209 : :
1210 : 4 : struct pl_frame inferred_image = image, inferred_target = target;
1211 : 4 : pl_frames_infer(rr, &inferred_image, &inferred_target);
1212 [ - + ]: 4 : REQUIRE(pl_render_image(rr, &inferred_image, &inferred_target, ¶ms));
1213 [ - + ]: 4 : REQUIRE(pl_renderer_get_errors(rr).errors == PL_RENDER_ERR_NONE);
1214 : :
1215 : : // Test background blending and alpha transparency
1216 : 4 : params.blend_against_tiles = true;
1217 : 4 : params.corner_rounding = 0.25f;
1218 [ - + ]: 4 : REQUIRE(pl_render_image(rr, &image, &target, ¶ms));
1219 [ - + ]: 4 : REQUIRE(pl_renderer_get_errors(rr).errors == PL_RENDER_ERR_NONE);
1220 : 4 : params = pl_render_default_params;
1221 : :
1222 : : // Test film grain synthesis
1223 : 4 : image.film_grain.type = PL_FILM_GRAIN_AV1;
1224 : 4 : image.film_grain.params.av1 = av1_grain_data;
1225 [ - + ]: 4 : REQUIRE(pl_render_image(rr, &image, &target, ¶ms));
1226 [ - + ]: 4 : REQUIRE(pl_renderer_get_errors(rr).errors == PL_RENDER_ERR_NONE);
1227 : :
1228 : 4 : image.film_grain.type = PL_FILM_GRAIN_H274;
1229 : 4 : image.film_grain.params.h274 = h274_grain_data;
1230 [ - + ]: 4 : REQUIRE(pl_render_image(rr, &image, &target, ¶ms));
1231 : : // H.274 film grain synthesis requires compute shaders
1232 [ + + ]: 4 : if (gpu->glsl.compute) {
1233 [ - + ]: 2 : REQUIRE(pl_renderer_get_errors(rr).errors == PL_RENDER_ERR_NONE);
1234 : : } else {
1235 : 2 : const struct pl_render_errors rr_err = pl_renderer_get_errors(rr);
1236 [ - + ]: 2 : REQUIRE(pl_renderer_get_errors(rr).errors == PL_RENDER_ERR_FILM_GRAIN);
1237 : 2 : pl_renderer_reset_errors(rr, &rr_err);
1238 : : }
1239 : 4 : image.film_grain = (struct pl_film_grain_data) {0};
1240 : :
1241 : : // Test mpv-style custom shaders
1242 [ + + ]: 12 : for (int i = 0; i < PL_ARRAY_SIZE(user_shader_tests); i++) {
1243 : 8 : printf("- testing user shader:\n\n%s\n", user_shader_tests[i]);
1244 : : const struct pl_hook *hook;
1245 : 8 : hook = pl_mpv_user_shader_parse(gpu, user_shader_tests[i],
1246 : : strlen(user_shader_tests[i]));
1247 [ - + ]: 8 : REQUIRE(hook);
1248 : :
1249 : 8 : params.hooks = &hook;
1250 : 8 : params.num_hooks = 1;
1251 [ - + ]: 8 : REQUIRE(pl_render_image(rr, &image, &target, ¶ms));
1252 [ - + ]: 8 : REQUIRE(pl_renderer_get_errors(rr).errors == PL_RENDER_ERR_NONE);
1253 : :
1254 : 8 : pl_mpv_user_shader_destroy(&hook);
1255 : : }
1256 : :
1257 [ + + + - ]: 4 : if (gpu->glsl.compute && gpu->limits.max_ssbo_size) {
1258 [ + + ]: 4 : for (int i = 0; i < PL_ARRAY_SIZE(compute_shader_tests); i++) {
1259 : 2 : printf("- testing user shader:\n\n%s\n", compute_shader_tests[i]);
1260 : : const struct pl_hook *hook;
1261 : 2 : hook = pl_mpv_user_shader_parse(gpu, compute_shader_tests[i],
1262 : : strlen(compute_shader_tests[i]));
1263 [ - + ]: 2 : REQUIRE(hook);
1264 : :
1265 : 2 : params.hooks = &hook;
1266 : 2 : params.num_hooks = 1;
1267 [ - + ]: 2 : REQUIRE(pl_render_image(rr, &image, &target, ¶ms));
1268 [ - + ]: 2 : REQUIRE(pl_renderer_get_errors(rr).errors == PL_RENDER_ERR_NONE);
1269 : :
1270 : 2 : pl_mpv_user_shader_destroy(&hook);
1271 : : }
1272 : : }
1273 : 4 : params = pl_render_default_params;
1274 : :
1275 : : // Test custom LUTs
1276 [ + + ]: 12 : for (int i = 0; i < PL_ARRAY_SIZE(test_luts); i++) {
1277 : : printf("- testing custom lut %d\n", i);
1278 : : struct pl_custom_lut *lut;
1279 : 8 : lut = pl_lut_parse_cube(gpu->log, test_luts[i], strlen(test_luts[i]));
1280 [ - + ]: 8 : REQUIRE(lut);
1281 : :
1282 [ + - - + ]: 8 : bool has_3dlut = gpu->limits.max_tex_3d_dim && gpu->glsl.version > 100;
1283 [ + + - + ]: 8 : if (lut->size[2] && !has_3dlut) {
1284 : 0 : pl_lut_free(&lut);
1285 : 0 : continue;
1286 : : }
1287 : :
1288 : : // Test all three at the same time to reduce the number of tests
1289 : 8 : image.lut = target.lut = params.lut = lut;
1290 : :
1291 [ + + ]: 40 : for (enum pl_lut_type t = PL_LUT_UNKNOWN; t <= PL_LUT_CONVERSION; t++) {
1292 : : printf("- testing LUT method %d\n", t);
1293 : 32 : image.lut_type = target.lut_type = params.lut_type = t;
1294 [ - + ]: 32 : REQUIRE(pl_render_image(rr, &image, &target, ¶ms));
1295 [ - + ]: 32 : REQUIRE(pl_renderer_get_errors(rr).errors == PL_RENDER_ERR_NONE);
1296 : : }
1297 : :
1298 : 8 : image.lut = target.lut = params.lut = NULL;
1299 : 8 : pl_lut_free(&lut);
1300 : : }
1301 : :
1302 : : #ifdef PL_HAVE_LCMS
1303 : :
1304 : : // It doesn't fit without use of 3D textures on GLES2
1305 [ + - ]: 4 : if (gpu->glsl.version > 100) {
1306 : : // Test ICC profiles
1307 : 4 : image.profile = TEST_PROFILE(sRGB_v2_nano_icc);
1308 [ - + ]: 4 : REQUIRE(pl_render_image(rr, &image, &target, ¶ms));
1309 [ - + ]: 4 : REQUIRE(pl_renderer_get_errors(rr).errors == PL_RENDER_ERR_NONE);
1310 : 4 : image.profile = (struct pl_icc_profile) {0};
1311 : :
1312 : 4 : target.profile = TEST_PROFILE(sRGB_v2_nano_icc);
1313 [ - + ]: 4 : REQUIRE(pl_render_image(rr, &image, &target, ¶ms));
1314 [ - + ]: 4 : REQUIRE(pl_renderer_get_errors(rr).errors == PL_RENDER_ERR_NONE);
1315 : : target.profile = (struct pl_icc_profile) {0};
1316 : :
1317 : 4 : image.profile = TEST_PROFILE(sRGB_v2_nano_icc);
1318 : 4 : target.profile = image.profile;
1319 [ - + ]: 4 : REQUIRE(pl_render_image(rr, &image, &target, ¶ms));
1320 [ - + ]: 4 : REQUIRE(pl_renderer_get_errors(rr).errors == PL_RENDER_ERR_NONE);
1321 : 4 : image.profile = (struct pl_icc_profile) {0};
1322 : 4 : target.profile = (struct pl_icc_profile) {0};
1323 : : }
1324 : :
1325 : : #endif
1326 : :
1327 : : // Test overlays
1328 : 4 : image.num_overlays = 1;
1329 : 4 : image.overlays = &(struct pl_overlay) {
1330 : 4 : .tex = img_plane.texture,
1331 : : .mode = PL_OVERLAY_NORMAL,
1332 : : .num_parts = 2,
1333 : 4 : .parts = (struct pl_overlay_part[]) {{
1334 : : .src = {0, 0, 2, 2},
1335 : : .dst = {30, 100, 40, 200},
1336 : : }, {
1337 : : .src = {2, 2, 5, 5},
1338 : : .dst = {1000, -1, 3, 5},
1339 : : }},
1340 : : };
1341 [ - + ]: 4 : REQUIRE(pl_render_image(rr, &image, &target, ¶ms));
1342 [ - + ]: 4 : REQUIRE(pl_renderer_get_errors(rr).errors == PL_RENDER_ERR_NONE);
1343 : 4 : params.disable_fbos = true;
1344 [ - + ]: 4 : REQUIRE(pl_render_image(rr, &image, &target, ¶ms));
1345 [ - + ]: 4 : REQUIRE(pl_renderer_get_errors(rr).errors == PL_RENDER_ERR_NONE);
1346 : 4 : image.num_overlays = 0;
1347 : 4 : params = pl_render_default_params;
1348 : :
1349 : 4 : target.num_overlays = 1;
1350 : 4 : target.overlays = &(struct pl_overlay) {
1351 : 4 : .tex = img_plane.texture,
1352 : : .mode = PL_OVERLAY_MONOCHROME,
1353 : : .num_parts = 1,
1354 : 4 : .parts = &(struct pl_overlay_part) {
1355 : : .src = {5, 5, 15, 15},
1356 : : .dst = {5, 5, 15, 15},
1357 : : .color = {1.0, 0.5, 0.0},
1358 : : },
1359 : : };
1360 [ - + ]: 4 : REQUIRE(pl_render_image(rr, &image, &target, ¶ms));
1361 [ - + ]: 4 : REQUIRE(pl_renderer_get_errors(rr).errors == PL_RENDER_ERR_NONE);
1362 [ - + ]: 4 : REQUIRE(pl_render_image(rr, NULL, &target, ¶ms));
1363 [ - + ]: 4 : REQUIRE(pl_renderer_get_errors(rr).errors == PL_RENDER_ERR_NONE);
1364 : 4 : target.num_overlays = 0;
1365 : :
1366 : : // Test rotation
1367 [ + + ]: 20 : for (pl_rotation rot = 0; rot < PL_ROTATION_360; rot += PL_ROTATION_90) {
1368 : 16 : image.rotation = rot;
1369 [ - + ]: 16 : REQUIRE(pl_render_image(rr, &image, &target, ¶ms));
1370 [ - + ]: 16 : REQUIRE(pl_renderer_get_errors(rr).errors == PL_RENDER_ERR_NONE);
1371 : : }
1372 : :
1373 : : // Attempt frame mixing, using the mixer queue helper
1374 : : printf("- testing frame mixing\n");
1375 : 4 : struct pl_render_params mix_params = {
1376 : : .frame_mixer = &pl_filter_mitchell_clamp,
1377 : : .info_callback = render_info_cb,
1378 : : };
1379 : :
1380 [ + - ]: 4 : struct pl_queue_params qparams = {
1381 : : .radius = pl_frame_mix_radius(&mix_params),
1382 : : .vsync_duration = 1.0 / 60.0,
1383 : : };
1384 : :
1385 : : // Test large PTS jumps in frame mix
1386 : 4 : struct pl_frame_mix mix = (struct pl_frame_mix) {
1387 : : .num_frames = 2,
1388 : 4 : .frames = (const struct pl_frame *[]) { &image, &image },
1389 : 4 : .signatures = (uint64_t[]) { 0xFFF1, 0xFFF2 },
1390 : 4 : .timestamps = (float[]) { -100, 100 },
1391 : : .vsync_duration = 1.6,
1392 : : };
1393 [ - + ]: 4 : REQUIRE(pl_render_image_mix(rr, &mix, &target, &mix_params));
1394 : :
1395 : : // Test inferring frame mix
1396 : 4 : inferred_target = target;
1397 : 4 : pl_frames_infer_mix(rr, &mix, &inferred_target, &inferred_image);
1398 [ - + ]: 4 : REQUIRE(pl_render_image_mix(rr, &mix, &target, &mix_params));
1399 : :
1400 : : // Test empty frame mix
1401 : 4 : mix = (struct pl_frame_mix) {0};
1402 [ - + ]: 4 : REQUIRE(pl_render_image_mix(rr, &mix, &target, &mix_params));
1403 : :
1404 : : // Test inferring empty frame mix
1405 : 4 : inferred_target = target;
1406 : 4 : pl_frames_infer_mix(rr, &mix, &inferred_target, &inferred_image);
1407 [ - + ]: 4 : REQUIRE(pl_render_image_mix(rr, &mix, &target, &mix_params));
1408 : :
1409 : : // Test mixer queue
1410 : : #define NUM_MIX_FRAMES 20
1411 : : const float frame_duration = 1.0 / 24.0;
1412 : : struct pl_source_frame srcframes[NUM_MIX_FRAMES+1];
1413 : 4 : srcframes[NUM_MIX_FRAMES] = (struct pl_source_frame) {0};
1414 [ + + ]: 84 : for (int i = 0; i < NUM_MIX_FRAMES; i++) {
1415 : 80 : srcframes[i] = (struct pl_source_frame) {
1416 : 80 : .pts = i * frame_duration,
1417 : : .duration = frame_duration,
1418 : : .map = frame_passthrough,
1419 : : .frame_data = &image,
1420 : : };
1421 : : }
1422 : :
1423 : 4 : pl_queue queue = pl_queue_create(gpu);
1424 : : enum pl_queue_status ret;
1425 : :
1426 : : // Test pre-pushing all frames, with delayed EOF.
1427 [ + + ]: 84 : for (int i = 0; i < NUM_MIX_FRAMES; i++) {
1428 : 80 : const struct pl_source_frame *src = &srcframes[i];
1429 [ + + ]: 80 : if (i > 10) // test pushing in reverse order
1430 : 36 : src = &srcframes[NUM_MIX_FRAMES + 10 - i];
1431 [ + + ]: 80 : if (!pl_queue_push_block(queue, 1, src)) // mini-sleep
1432 : 72 : pl_queue_push(queue, src); // push it anyway, for testing
1433 : : }
1434 : :
1435 [ + + ]: 208 : while ((ret = pl_queue_update(queue, &mix, &qparams)) != PL_QUEUE_EOF) {
1436 [ + + ]: 204 : if (ret == PL_QUEUE_MORE) {
1437 [ - + ]: 4 : REQUIRE_CMP(qparams.pts, >, 0.0f, "f");
1438 : 4 : pl_queue_push(queue, NULL); // push delayed EOF
1439 : 4 : continue;
1440 : : }
1441 : :
1442 [ - + ]: 200 : REQUIRE_CMP(ret, ==, PL_QUEUE_OK, "u");
1443 [ - + ]: 200 : REQUIRE(pl_render_image_mix(rr, &mix, &target, &mix_params));
1444 : :
1445 : : // Simulate advancing vsync
1446 : 200 : qparams.pts += qparams.vsync_duration;
1447 : : }
1448 : :
1449 : : // Test dynamically pulling all frames, with oversample mixer
1450 : 4 : const struct pl_source_frame *frame_ptr = &srcframes[0];
1451 [ + - ]: 4 : mix_params.frame_mixer = &pl_oversample_frame_mixer;
1452 : :
1453 : 4 : qparams = (struct pl_queue_params) {
1454 : : .radius = pl_frame_mix_radius(&mix_params),
1455 : 4 : .vsync_duration = qparams.vsync_duration,
1456 : : .get_frame = get_frame_ptr,
1457 : : .priv = &frame_ptr,
1458 : : };
1459 : :
1460 : 4 : pl_queue_reset(queue);
1461 [ + + ]: 204 : while ((ret = pl_queue_update(queue, &mix, &qparams)) != PL_QUEUE_EOF) {
1462 [ - + ]: 200 : REQUIRE_CMP(ret, ==, PL_QUEUE_OK, "u");
1463 [ - + ]: 200 : REQUIRE_CMP(mix.num_frames, <=, 2, "d");
1464 [ - + ]: 200 : REQUIRE(pl_render_image_mix(rr, &mix, &target, &mix_params));
1465 : 200 : qparams.pts += qparams.vsync_duration;
1466 : : }
1467 : :
1468 : : // Test large PTS jump
1469 : 4 : pl_queue_reset(queue);
1470 [ - + ]: 4 : REQUIRE(pl_queue_update(queue, &mix, &qparams) == PL_QUEUE_EOF);
1471 : :
1472 : : // Test deinterlacing
1473 : 4 : pl_queue_reset(queue);
1474 : : printf("- testing deinterlacing\n");
1475 [ + + ]: 84 : for (int i = 0; i < NUM_MIX_FRAMES; i++) {
1476 : 80 : struct pl_source_frame *src = &srcframes[i];
1477 [ + + ]: 80 : if (i > 10)
1478 : 36 : src = &srcframes[NUM_MIX_FRAMES + 10 - i];
1479 : 80 : src->first_field = PL_FIELD_EVEN;
1480 : 80 : pl_queue_push(queue, src);
1481 : : }
1482 : 4 : pl_queue_push(queue, NULL);
1483 : :
1484 : 4 : qparams.pts = 0;
1485 : 4 : qparams.get_frame = NULL;
1486 [ + + ]: 204 : while ((ret = pl_queue_update(queue, &mix, &qparams)) != PL_QUEUE_EOF) {
1487 [ - + ]: 200 : REQUIRE_CMP(ret, ==, PL_QUEUE_OK, "u");
1488 [ - + ]: 200 : REQUIRE(pl_render_image_mix(rr, &mix, &target, &mix_params));
1489 : 200 : qparams.pts += qparams.vsync_duration;
1490 : : }
1491 : :
1492 : 4 : pl_queue_destroy(&queue);
1493 : :
1494 : 4 : error:
1495 : 4 : pl_renderer_destroy(&rr);
1496 : 4 : pl_tex_destroy(gpu, &img_tex);
1497 : 4 : pl_tex_destroy(gpu, &fbo);
1498 : : }
1499 : :
1500 : 5 : static struct pl_hook_res noop_hook(void *priv, const struct pl_hook_params *params)
1501 : : {
1502 : 5 : return (struct pl_hook_res) {0};
1503 : : }
1504 : :
1505 : 5 : static void pl_ycbcr_tests(pl_gpu gpu)
1506 : : {
1507 : : struct pl_plane_data data[3];
1508 [ + + ]: 20 : for (int i = 0; i < 3; i++) {
1509 : 15 : const int sub = i > 0 ? 1 : 0;
1510 : 15 : const int width = (323 + sub) >> sub;
1511 : 15 : const int height = (255 + sub) >> sub;
1512 : :
1513 : 15 : data[i] = (struct pl_plane_data) {
1514 : : .type = PL_FMT_UNORM,
1515 : : .width = width,
1516 : : .height = height,
1517 : : .component_size = {16},
1518 : : .component_map = {i},
1519 : : .pixel_stride = sizeof(uint16_t),
1520 : 15 : .row_stride = PL_ALIGN2(width * sizeof(uint16_t),
1521 : : gpu->limits.align_tex_xfer_pitch),
1522 : : };
1523 : : }
1524 : :
1525 : 5 : pl_fmt fmt = pl_plane_find_fmt(gpu, NULL, &data[0]);
1526 : : enum pl_fmt_caps caps = PL_FMT_CAP_RENDERABLE | PL_FMT_CAP_HOST_READABLE;
1527 [ + - + - ]: 5 : if (!fmt || (fmt->caps & caps) != caps)
1528 : 0 : return;
1529 : :
1530 : 5 : printf("pl_ycbcr_tests:\nfmt=%s\n", fmt->name);
1531 : :
1532 : 5 : pl_renderer rr = pl_renderer_create(gpu->log, gpu);
1533 [ + - ]: 5 : if (!rr)
1534 : : return;
1535 : :
1536 : 5 : pl_tex src_tex[3] = {0};
1537 : 5 : pl_tex dst_tex[3] = {0};
1538 : 5 : struct pl_frame img = {
1539 : : .num_planes = 3,
1540 : : .repr = pl_color_repr_hdtv,
1541 : : .color = pl_color_space_bt709,
1542 : : };
1543 : :
1544 : 5 : struct pl_frame target = {
1545 : : .num_planes = 3,
1546 : : .repr = pl_color_repr_hdtv,
1547 : : .color = pl_color_space_bt709,
1548 : : };
1549 : :
1550 : 5 : uint8_t *src_buffer[3] = {0};
1551 : : uint8_t *dst_buffer = NULL;
1552 [ + + ]: 20 : for (int i = 0; i < 3; i++) {
1553 : : // Generate some arbitrary data for the buffer
1554 : 15 : src_buffer[i] = malloc(data[i].height * data[i].row_stride);
1555 [ - + ]: 15 : if (!src_buffer[i])
1556 : 0 : goto error;
1557 : :
1558 : 15 : data[i].pixels = src_buffer[i];
1559 [ + + ]: 2570 : for (int y = 0; y < data[i].height; y++) {
1560 [ + + ]: 621740 : for (int x = 0; x < data[i].width; x++) {
1561 : 619185 : size_t off = y * data[i].row_stride + x * data[i].pixel_stride;
1562 : 619185 : uint16_t *pixel = (uint16_t *) &src_buffer[i][off];
1563 : 619185 : int gx = 200 + 100 * i, gy = 300 + 150 * i;
1564 : 619185 : *pixel = (gx * x) ^ (gy * y); // whatever
1565 : : }
1566 : : }
1567 : :
1568 [ - + ]: 15 : REQUIRE(pl_upload_plane(gpu, &img.planes[i], &src_tex[i], &data[i]));
1569 : : }
1570 : :
1571 : : // This co-sites chroma pixels with pixels in the RGB image, meaning we
1572 : : // get an exact round-trip when sampling both ways. This makes it useful
1573 : : // as a test case, even though it's not common in the real world.
1574 : 5 : pl_frame_set_chroma_location(&img, PL_CHROMA_TOP_LEFT);
1575 : :
1576 [ + + ]: 20 : for (int i = 0; i < 3; i++) {
1577 : 30 : dst_tex[i] = pl_tex_create(gpu, &(struct pl_tex_params) {
1578 : : .format = fmt,
1579 : 15 : .w = data[i].width,
1580 : 15 : .h = data[i].height,
1581 : : .renderable = true,
1582 : : .host_readable = true,
1583 : 15 : .storable = fmt->caps & PL_FMT_CAP_STORABLE,
1584 : 15 : .blit_dst = fmt->caps & PL_FMT_CAP_BLITTABLE,
1585 : : });
1586 : :
1587 [ - + ]: 15 : if (!dst_tex[i])
1588 : 0 : goto error;
1589 : :
1590 : 15 : target.planes[i] = img.planes[i];
1591 : 15 : target.planes[i].texture = dst_tex[i];
1592 : : }
1593 : :
1594 [ - + ]: 5 : REQUIRE(pl_render_image(rr, &img, &target, &(struct pl_render_params) {
1595 : : .num_hooks = 1,
1596 : : .hooks = &(const struct pl_hook *){&(struct pl_hook) {
1597 : : // Forces chroma merging, to test the chroma merging code
1598 : : .stages = PL_HOOK_CHROMA_INPUT,
1599 : : .hook = noop_hook,
1600 : : }},
1601 : : }));
1602 [ - + ]: 5 : REQUIRE(pl_renderer_get_errors(rr).errors == PL_RENDER_ERR_NONE);
1603 : :
1604 : 5 : size_t buf_size = data[0].height * data[0].row_stride;
1605 : 5 : dst_buffer = malloc(buf_size);
1606 [ - + ]: 5 : if (!dst_buffer)
1607 : 0 : goto error;
1608 : :
1609 [ + + ]: 20 : for (int i = 0; i < 3; i++) {
1610 : : memset(dst_buffer, 0xAA, buf_size);
1611 [ - + ]: 15 : REQUIRE(pl_tex_download(gpu, &(struct pl_tex_transfer_params) {
1612 : : .tex = dst_tex[i],
1613 : : .ptr = dst_buffer,
1614 : : .row_pitch = data[i].row_stride,
1615 : : }));
1616 : :
1617 [ + + ]: 2570 : for (int y = 0; y < data[i].height; y++) {
1618 [ + + ]: 621740 : for (int x = 0; x < data[i].width; x++) {
1619 : 619185 : size_t off = y * data[i].row_stride + x * data[i].pixel_stride;
1620 : 619185 : uint16_t *src_pixel = (uint16_t *) &src_buffer[i][off];
1621 : 619185 : uint16_t *dst_pixel = (uint16_t *) &dst_buffer[off];
1622 : 619185 : int diff = abs((int) *src_pixel - (int) *dst_pixel);
1623 [ - + ]: 619185 : REQUIRE_CMP(diff, <=, 150, "d"); // a little over 0.2%
1624 : : }
1625 : : }
1626 : : }
1627 : :
1628 : 5 : error:
1629 : 5 : pl_renderer_destroy(&rr);
1630 : 5 : free(dst_buffer);
1631 [ + + ]: 20 : for (int i = 0; i < 3; i++) {
1632 : 15 : free(src_buffer[i]);
1633 : 15 : pl_tex_destroy(gpu, &src_tex[i]);
1634 : 15 : pl_tex_destroy(gpu, &dst_tex[i]);
1635 : : }
1636 : : }
1637 : :
1638 : 6 : static void pl_test_export_import(pl_gpu gpu,
1639 : : enum pl_handle_type handle_type)
1640 : : {
1641 : : printf("pl_test_export_import:\n");
1642 : : // Test texture roundtrip
1643 : :
1644 [ + - ]: 6 : if (!(gpu->export_caps.tex & handle_type) ||
1645 [ - + ]: 6 : !(gpu->import_caps.tex & handle_type))
1646 : 0 : goto skip_tex;
1647 : :
1648 : 6 : pl_fmt fmt = pl_find_fmt(gpu, PL_FMT_UNORM, 4, 0, 0, PL_FMT_CAP_BLITTABLE);
1649 [ - + ]: 6 : if (!fmt)
1650 : 0 : goto skip_tex;
1651 : :
1652 : 6 : printf("- testing texture import/export with fmt %s\n", fmt->name);
1653 : :
1654 : 6 : pl_tex export = pl_tex_create(gpu, &(struct pl_tex_params) {
1655 : : .w = 32,
1656 : : .h = 32,
1657 : : .format = fmt,
1658 : : .export_handle = handle_type,
1659 : : });
1660 [ - + ]: 6 : REQUIRE(export);
1661 [ + - - - : 6 : REQUIRE_HANDLE(export->shared_mem, handle_type);
- - + - -
- - - - ]
1662 : :
1663 : 12 : pl_tex import = pl_tex_create(gpu, &(struct pl_tex_params) {
1664 : 6 : .w = export->params.w,
1665 : 6 : .h = export->params.h,
1666 : : .format = fmt,
1667 : : .import_handle = handle_type,
1668 : : .shared_mem = export->shared_mem,
1669 : : });
1670 [ - + ]: 6 : REQUIRE(import);
1671 : :
1672 : 6 : pl_tex_destroy(gpu, &import);
1673 : 6 : pl_tex_destroy(gpu, &export);
1674 : :
1675 : 6 : skip_tex: ;
1676 : :
1677 : : // Test buffer roundtrip
1678 : :
1679 [ + + ]: 6 : if (!(gpu->export_caps.buf & handle_type) ||
1680 [ - + ]: 2 : !(gpu->import_caps.buf & handle_type))
1681 : 4 : return;
1682 : :
1683 : : printf("- testing buffer import/export\n");
1684 : :
1685 : 2 : pl_buf exp_buf = pl_buf_create(gpu, &(struct pl_buf_params) {
1686 : : .size = 32,
1687 : : .export_handle = handle_type,
1688 : : });
1689 [ - + ]: 2 : REQUIRE(exp_buf);
1690 [ + - - - : 2 : REQUIRE_HANDLE(exp_buf->shared_mem, handle_type);
- - + - -
- - - - ]
1691 : :
1692 : 2 : pl_buf imp_buf = pl_buf_create(gpu, &(struct pl_buf_params) {
1693 : : .size = 32,
1694 : : .import_handle = handle_type,
1695 : : .shared_mem = exp_buf->shared_mem,
1696 : : });
1697 [ - + ]: 2 : REQUIRE(imp_buf);
1698 : :
1699 : 2 : pl_buf_destroy(gpu, &imp_buf);
1700 : 2 : pl_buf_destroy(gpu, &exp_buf);
1701 : : }
1702 : :
1703 : 6 : static void pl_test_host_ptr(pl_gpu gpu)
1704 : : {
1705 [ + + ]: 6 : if (!(gpu->import_caps.buf & PL_HANDLE_HOST_PTR))
1706 : 4 : return;
1707 : :
1708 : : printf("pl_test_host_ptr:\n");
1709 : :
1710 : : #ifdef __unix__
1711 : :
1712 : : printf("- testing host ptr\n");
1713 [ - + ]: 2 : REQUIRE(gpu->limits.max_mapped_size);
1714 : :
1715 : : const size_t size = 2 << 20;
1716 : : const size_t offset = 2 << 10;
1717 : : const size_t slice = 2 << 16;
1718 : :
1719 : 2 : uint8_t *data = aligned_alloc(0x1000, size);
1720 [ + + ]: 4194306 : for (int i = 0; i < size; i++)
1721 : 4194304 : data[i] = (uint8_t) i;
1722 : :
1723 : 2 : pl_buf buf = pl_buf_create(gpu, &(struct pl_buf_params) {
1724 : : .size = slice,
1725 : : .import_handle = PL_HANDLE_HOST_PTR,
1726 : : .shared_mem = {
1727 : : .handle.ptr = data,
1728 : : .size = size,
1729 : : .offset = offset,
1730 : : },
1731 : : .host_mapped = true,
1732 : : });
1733 : :
1734 [ - + ]: 2 : REQUIRE(buf);
1735 : 2 : REQUIRE_MEMEQ(data + offset, buf->data, slice);
1736 : :
1737 : 2 : pl_buf_destroy(gpu, &buf);
1738 : 2 : free(data);
1739 : :
1740 : : #endif // unix
1741 : : }
1742 : :
1743 : 5 : void gpu_shader_tests(pl_gpu gpu)
1744 : : {
1745 : 5 : pl_buffer_tests(gpu);
1746 : 5 : pl_texture_tests(gpu);
1747 : 5 : pl_planar_tests(gpu);
1748 : 5 : pl_shader_tests(gpu);
1749 : 5 : pl_scaler_tests(gpu);
1750 : 5 : pl_render_tests(gpu);
1751 : 5 : pl_ycbcr_tests(gpu);
1752 : :
1753 [ - + ]: 5 : REQUIRE(!pl_gpu_is_failed(gpu));
1754 : 5 : }
1755 : :
1756 : 6 : void gpu_interop_tests(pl_gpu gpu)
1757 : : {
1758 : 6 : pl_test_export_import(gpu, PL_HANDLE_DMA_BUF);
1759 : 6 : pl_test_host_ptr(gpu);
1760 : :
1761 [ - + ]: 6 : REQUIRE(!pl_gpu_is_failed(gpu));
1762 : 6 : }
|