Branch data Line data Source code
1 : : /*
2 : : * This file is part of libplacebo.
3 : : *
4 : : * libplacebo is free software; you can redistribute it and/or
5 : : * modify it under the terms of the GNU Lesser General Public
6 : : * License as published by the Free Software Foundation; either
7 : : * version 2.1 of the License, or (at your option) any later version.
8 : : *
9 : : * libplacebo is distributed in the hope that it will be useful,
10 : : * but WITHOUT ANY WARRANTY; without even the implied warranty of
11 : : * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 : : * GNU Lesser General Public License for more details.
13 : : *
14 : : * You should have received a copy of the GNU Lesser General Public
15 : : * License along with libplacebo. If not, see <http://www.gnu.org/licenses/>.
16 : : */
17 : :
18 : : #include <math.h>
19 : :
20 : : #include "common.h"
21 : : #include "filters.h"
22 : : #include "hash.h"
23 : : #include "shaders.h"
24 : : #include "dispatch.h"
25 : :
26 : : #include <libplacebo/renderer.h>
27 : :
28 : : struct cached_frame {
29 : : uint64_t signature;
30 : : uint64_t params_hash; // for detecting `pl_render_params` changes
31 : : struct pl_color_space color;
32 : : struct pl_icc_profile profile;
33 : : pl_rect2df crop;
34 : : pl_tex tex;
35 : : int comps;
36 : : bool evict; // for garbage collection
37 : : };
38 : :
39 : : struct sampler {
40 : : pl_shader_obj upscaler_state;
41 : : pl_shader_obj downscaler_state;
42 : : };
43 : :
44 : : struct osd_vertex {
45 : : float pos[2];
46 : : float coord[2];
47 : : float color[4];
48 : : };
49 : :
50 : : struct icc_state {
51 : : pl_icc_object icc;
52 : : uint64_t error; // set to profile signature on failure
53 : : };
54 : :
55 : : struct pl_renderer_t {
56 : : pl_gpu gpu;
57 : : pl_dispatch dp;
58 : : pl_log log;
59 : :
60 : : // Cached feature checks (inverted)
61 : : enum pl_render_error errors;
62 : :
63 : : // List containing signatures of disabled hooks
64 : : PL_ARRAY(uint64_t) disabled_hooks;
65 : :
66 : : // Shader resource objects and intermediate textures (FBOs)
67 : : pl_shader_obj tone_map_state;
68 : : pl_shader_obj dither_state;
69 : : pl_shader_obj grain_state[4];
70 : : pl_shader_obj lut_state[3];
71 : : pl_shader_obj icc_state[2];
72 : : PL_ARRAY(pl_tex) fbos;
73 : : struct sampler sampler_main;
74 : : struct sampler sampler_contrast;
75 : : struct sampler samplers_src[4];
76 : : struct sampler samplers_dst[4];
77 : :
78 : : // Temporary storage for vertex/index data
79 : : PL_ARRAY(struct osd_vertex) osd_vertices;
80 : : PL_ARRAY(uint16_t) osd_indices;
81 : : struct pl_vertex_attrib osd_attribs[3];
82 : :
83 : : // Frame cache (for frame mixing / interpolation)
84 : : PL_ARRAY(struct cached_frame) frames;
85 : : PL_ARRAY(pl_tex) frame_fbos;
86 : :
87 : : // For debugging / logging purposes
88 : : int prev_dither;
89 : :
90 : : // For backwards compatibility
91 : : struct icc_state icc_fallback[2];
92 : : };
93 : :
94 : : enum {
95 : : // Index into `lut_state`
96 : : LUT_IMAGE,
97 : : LUT_TARGET,
98 : : LUT_PARAMS,
99 : : };
100 : :
101 : : enum {
102 : : // Index into `icc_state`
103 : : ICC_IMAGE,
104 : : ICC_TARGET
105 : : };
106 : :
107 : 9 : pl_renderer pl_renderer_create(pl_log log, pl_gpu gpu)
108 : : {
109 : 9 : pl_renderer rr = pl_alloc_ptr(NULL, rr);
110 : 9 : *rr = (struct pl_renderer_t) {
111 : : .gpu = gpu,
112 : : .log = log,
113 : 9 : .dp = pl_dispatch_create(log, gpu),
114 : : .osd_attribs = {
115 : : {
116 : : .name = "pos",
117 : : .offset = offsetof(struct osd_vertex, pos),
118 : 9 : .fmt = pl_find_vertex_fmt(gpu, PL_FMT_FLOAT, 2),
119 : : }, {
120 : : .name = "coord",
121 : : .offset = offsetof(struct osd_vertex, coord),
122 : 9 : .fmt = pl_find_vertex_fmt(gpu, PL_FMT_FLOAT, 2),
123 : : }, {
124 : : .name = "osd_color",
125 : : .offset = offsetof(struct osd_vertex, color),
126 : 9 : .fmt = pl_find_vertex_fmt(gpu, PL_FMT_FLOAT, 4),
127 : : }
128 : : },
129 : : };
130 : :
131 [ - + ]: 9 : assert(rr->dp);
132 : 9 : return rr;
133 : : }
134 : :
135 : : static void sampler_destroy(pl_renderer rr, struct sampler *sampler)
136 : : {
137 : 90 : pl_shader_obj_destroy(&sampler->upscaler_state);
138 : 90 : pl_shader_obj_destroy(&sampler->downscaler_state);
139 : : }
140 : :
141 : 9 : void pl_renderer_destroy(pl_renderer *p_rr)
142 : : {
143 : 9 : pl_renderer rr = *p_rr;
144 [ + - ]: 9 : if (!rr)
145 : : return;
146 : :
147 : : // Free all intermediate FBOs
148 [ + + ]: 35 : for (int i = 0; i < rr->fbos.num; i++)
149 : 26 : pl_tex_destroy(rr->gpu, &rr->fbos.elem[i]);
150 [ + + ]: 13 : for (int i = 0; i < rr->frames.num; i++)
151 : 4 : pl_tex_destroy(rr->gpu, &rr->frames.elem[i].tex);
152 [ + + ]: 21 : for (int i = 0; i < rr->frame_fbos.num; i++)
153 : 12 : pl_tex_destroy(rr->gpu, &rr->frame_fbos.elem[i]);
154 : :
155 : : // Free all shader resource objects
156 : 9 : pl_shader_obj_destroy(&rr->tone_map_state);
157 : 9 : pl_shader_obj_destroy(&rr->dither_state);
158 [ + + ]: 36 : for (int i = 0; i < PL_ARRAY_SIZE(rr->lut_state); i++)
159 : 27 : pl_shader_obj_destroy(&rr->lut_state[i]);
160 [ + + ]: 45 : for (int i = 0; i < PL_ARRAY_SIZE(rr->grain_state); i++)
161 : 36 : pl_shader_obj_destroy(&rr->grain_state[i]);
162 [ + + ]: 27 : for (int i = 0; i < PL_ARRAY_SIZE(rr->icc_state); i++)
163 : 18 : pl_shader_obj_destroy(&rr->icc_state[i]);
164 : :
165 : : // Free all samplers
166 : : sampler_destroy(rr, &rr->sampler_main);
167 : : sampler_destroy(rr, &rr->sampler_contrast);
168 [ + + ]: 45 : for (int i = 0; i < PL_ARRAY_SIZE(rr->samplers_src); i++)
169 : : sampler_destroy(rr, &rr->samplers_src[i]);
170 [ + + ]: 45 : for (int i = 0; i < PL_ARRAY_SIZE(rr->samplers_dst); i++)
171 : : sampler_destroy(rr, &rr->samplers_dst[i]);
172 : :
173 : : // Free fallback ICC profiles
174 [ + + ]: 27 : for (int i = 0; i < PL_ARRAY_SIZE(rr->icc_fallback); i++)
175 : 18 : pl_icc_close(&rr->icc_fallback[i].icc);
176 : :
177 : 9 : pl_dispatch_destroy(&rr->dp);
178 : 9 : pl_free_ptr(p_rr);
179 : : }
180 : :
181 : 0 : size_t pl_renderer_save(pl_renderer rr, uint8_t *out)
182 : : {
183 [ # # ]: 0 : return pl_cache_save(pl_gpu_cache(rr->gpu), out, out ? SIZE_MAX : 0);
184 : : }
185 : :
186 : 0 : void pl_renderer_load(pl_renderer rr, const uint8_t *cache)
187 : : {
188 : 0 : pl_cache_load(pl_gpu_cache(rr->gpu), cache, SIZE_MAX);
189 : 0 : }
190 : :
191 : 0 : void pl_renderer_flush_cache(pl_renderer rr)
192 : : {
193 [ # # ]: 0 : for (int i = 0; i < rr->frames.num; i++)
194 : 0 : pl_tex_destroy(rr->gpu, &rr->frames.elem[i].tex);
195 : 0 : rr->frames.num = 0;
196 : :
197 : 0 : pl_reset_detected_peak(rr->tone_map_state);
198 : 0 : }
199 : :
200 : : const struct pl_render_params pl_render_fast_params = { PL_RENDER_DEFAULTS };
201 : : const struct pl_render_params pl_render_default_params = {
202 : : PL_RENDER_DEFAULTS
203 : : .upscaler = &pl_filter_lanczos,
204 : : .downscaler = &pl_filter_hermite,
205 : : .frame_mixer = &pl_filter_oversample,
206 : : .sigmoid_params = &pl_sigmoid_default_params,
207 : : .dither_params = &pl_dither_default_params,
208 : : .peak_detect_params = &pl_peak_detect_default_params,
209 : : };
210 : :
211 : : const struct pl_render_params pl_render_high_quality_params = {
212 : : PL_RENDER_DEFAULTS
213 : : .upscaler = &pl_filter_ewa_lanczossharp,
214 : : .downscaler = &pl_filter_hermite,
215 : : .frame_mixer = &pl_filter_oversample,
216 : : .sigmoid_params = &pl_sigmoid_default_params,
217 : : .peak_detect_params = &pl_peak_detect_high_quality_params,
218 : : .color_map_params = &pl_color_map_high_quality_params,
219 : : .dither_params = &pl_dither_default_params,
220 : : .deband_params = &pl_deband_default_params,
221 : : };
222 : :
223 : : const struct pl_filter_preset pl_frame_mixers[] = {
224 : : { "none", NULL, "No frame mixing" },
225 : : { "linear", &pl_filter_bilinear, "Linear frame mixing" },
226 : : { "oversample", &pl_filter_oversample, "Oversample (AKA SmoothMotion)" },
227 : : { "mitchell_clamp", &pl_filter_mitchell_clamp, "Clamped Mitchell spline" },
228 : : { "hermite", &pl_filter_hermite, "Cubic spline (Hermite)" },
229 : : {0}
230 : : };
231 : :
232 : : const int pl_num_frame_mixers = PL_ARRAY_SIZE(pl_frame_mixers) - 1;
233 : :
234 : : const struct pl_filter_preset pl_scale_filters[] = {
235 : : {"none", NULL, "Built-in sampling"},
236 : : {"oversample", &pl_filter_oversample, "Oversample (Aspect-preserving NN)"},
237 : : COMMON_FILTER_PRESETS,
238 : : {0}
239 : : };
240 : :
241 : : const int pl_num_scale_filters = PL_ARRAY_SIZE(pl_scale_filters) - 1;
242 : :
243 : : // Represents a "in-flight" image, which is either a shader that's in the
244 : : // process of producing some sort of image, or a texture that needs to be
245 : : // sampled from
246 : : struct img {
247 : : // Effective texture size, always set
248 : : int w, h;
249 : :
250 : : // Recommended format (falls back to fbofmt otherwise), only for shaders
251 : : pl_fmt fmt;
252 : :
253 : : // Exactly *one* of these two is set:
254 : : pl_shader sh;
255 : : pl_tex tex;
256 : :
257 : : // If true, created shaders will be set to unique
258 : : bool unique;
259 : :
260 : : // Information about what to log/disable/fallback to if the shader fails
261 : : const char *err_msg;
262 : : enum pl_render_error err_enum;
263 : : pl_tex err_tex;
264 : :
265 : : // Current effective source area, will be sampled by the main scaler
266 : : pl_rect2df rect;
267 : :
268 : : // The current effective colorspace
269 : : struct pl_color_repr repr;
270 : : struct pl_color_space color;
271 : : int comps;
272 : : };
273 : :
274 : : // Plane 'type', ordered by incrementing priority
275 : : enum plane_type {
276 : : PLANE_INVALID = 0,
277 : : PLANE_ALPHA,
278 : : PLANE_CHROMA,
279 : : PLANE_LUMA,
280 : : PLANE_RGB,
281 : : PLANE_XYZ,
282 : : };
283 : :
284 : 7557 : static inline enum plane_type detect_plane_type(const struct pl_plane *plane,
285 : : const struct pl_color_repr *repr)
286 : : {
287 [ + + ]: 7557 : if (pl_color_system_is_ycbcr_like(repr->sys)) {
288 : : int t = PLANE_INVALID;
289 [ + + ]: 7306 : for (int c = 0; c < plane->components; c++) {
290 [ + - + - ]: 3653 : switch (plane->component_mapping[c]) {
291 : 3643 : case PL_CHANNEL_Y: t = PL_MAX(t, PLANE_LUMA); continue;
292 : 0 : case PL_CHANNEL_A: t = PL_MAX(t, PLANE_ALPHA); continue;
293 : :
294 : 10 : case PL_CHANNEL_CB:
295 : : case PL_CHANNEL_CR:
296 : 10 : t = PL_MAX(t, PLANE_CHROMA);
297 : 10 : continue;
298 : :
299 : 0 : default: continue;
300 : : }
301 : : }
302 : :
303 [ - + ]: 3653 : pl_assert(t);
304 : 3653 : return t;
305 : : }
306 : :
307 : : // Extra test for exclusive / separated alpha plane
308 [ + - ]: 3904 : if (plane->components == 1 && plane->component_mapping[0] == PL_CHANNEL_A)
309 : : return PLANE_ALPHA;
310 : :
311 [ - - + ]: 3904 : switch (repr->sys) {
312 : : case PL_COLOR_SYSTEM_UNKNOWN: // fall through to RGB
313 : : case PL_COLOR_SYSTEM_RGB: return PLANE_RGB;
314 : 0 : case PL_COLOR_SYSTEM_XYZ: return PLANE_XYZ;
315 : :
316 : : // For the switch completeness check
317 : : case PL_COLOR_SYSTEM_BT_601:
318 : : case PL_COLOR_SYSTEM_BT_709:
319 : : case PL_COLOR_SYSTEM_SMPTE_240M:
320 : : case PL_COLOR_SYSTEM_BT_2020_NC:
321 : : case PL_COLOR_SYSTEM_BT_2020_C:
322 : : case PL_COLOR_SYSTEM_BT_2100_PQ:
323 : : case PL_COLOR_SYSTEM_BT_2100_HLG:
324 : : case PL_COLOR_SYSTEM_DOLBYVISION:
325 : : case PL_COLOR_SYSTEM_YCGCO:
326 : : case PL_COLOR_SYSTEM_COUNT:
327 : : break;
328 : : }
329 : :
330 : 0 : pl_unreachable();
331 : : }
332 : :
333 : : struct pass_state {
334 : : void *tmp;
335 : : pl_renderer rr;
336 : : const struct pl_render_params *params;
337 : : struct pl_render_info info; // for info callback
338 : :
339 : : // Represents the "current" image which we're in the process of rendering.
340 : : // This is initially set by pass_read_image, and all of the subsequent
341 : : // rendering steps will mutate this in-place.
342 : : struct img img;
343 : :
344 : : // Represents the "reference rect". Canonically, this is functionally
345 : : // equivalent to `image.crop`, but also updates as the refplane evolves
346 : : // (e.g. due to user hook prescalers)
347 : : pl_rect2df ref_rect;
348 : :
349 : : // Integer version of `target.crop`. Semantically identical.
350 : : pl_rect2d dst_rect;
351 : :
352 : : // Logical end-to-end rotation
353 : : pl_rotation rotation;
354 : :
355 : : // Cached copies of the `image` / `target` for this rendering pass,
356 : : // corrected to make sure all rects etc. are properly defaulted/inferred.
357 : : struct pl_frame image;
358 : : struct pl_frame target;
359 : :
360 : : // Cached copies of the `prev` / `next` frames, for deinterlacing.
361 : : struct pl_frame prev, next;
362 : :
363 : : // Some extra plane metadata, inferred from `planes`
364 : : enum plane_type src_type[4];
365 : : int src_ref, dst_ref; // index into `planes`
366 : :
367 : : // Metadata for `rr->fbos`
368 : : pl_fmt fbofmt[5];
369 : : bool *fbos_used;
370 : : bool need_peak_fbo; // need indirection for peak detection
371 : :
372 : : // Map of acquired frames
373 : : struct {
374 : : bool target, image, prev, next;
375 : : } acquired;
376 : : };
377 : :
378 : 1409 : static void find_fbo_format(struct pass_state *pass)
379 : : {
380 : 1409 : const struct pl_render_params *params = pass->params;
381 : 1409 : pl_renderer rr = pass->rr;
382 [ + + + - : 1409 : if (params->disable_fbos || (rr->errors & PL_RENDER_ERR_FBO) || pass->fbofmt[4])
+ + ]
383 : 1409 : return;
384 : :
385 : : struct {
386 : : enum pl_fmt_type type;
387 : : int depth;
388 : : enum pl_fmt_caps caps;
389 : 1085 : } configs[] = {
390 : : // Prefer floating point formats first
391 : : {PL_FMT_FLOAT, 16, PL_FMT_CAP_LINEAR},
392 : : {PL_FMT_FLOAT, 16, PL_FMT_CAP_SAMPLEABLE},
393 : :
394 : : // Otherwise, fall back to unorm/snorm, preferring linearly sampleable
395 : : {PL_FMT_UNORM, 16, PL_FMT_CAP_LINEAR},
396 : : {PL_FMT_SNORM, 16, PL_FMT_CAP_LINEAR},
397 : : {PL_FMT_UNORM, 16, PL_FMT_CAP_SAMPLEABLE},
398 : : {PL_FMT_SNORM, 16, PL_FMT_CAP_SAMPLEABLE},
399 : :
400 : : // As a final fallback, allow 8-bit FBO formats (for UNORM only)
401 : : {PL_FMT_UNORM, 8, PL_FMT_CAP_LINEAR},
402 : : {PL_FMT_UNORM, 8, PL_FMT_CAP_SAMPLEABLE},
403 : : };
404 : :
405 : : pl_fmt fmt = NULL;
406 [ + - ]: 1085 : for (int i = 0; i < PL_ARRAY_SIZE(configs); i++) {
407 [ - + - - ]: 1085 : if (params->force_low_bit_depth_fbos && configs[i].depth > 8)
408 : 0 : continue;
409 : :
410 : 1085 : fmt = pl_find_fmt(rr->gpu, configs[i].type, 4, configs[i].depth, 0,
411 : 1085 : PL_FMT_CAP_RENDERABLE | configs[i].caps);
412 [ - + ]: 1085 : if (!fmt)
413 : 0 : continue;
414 : :
415 : 1085 : pass->fbofmt[4] = fmt;
416 : :
417 : : // Probe the right variant for each number of channels, falling
418 : : // back to the next biggest format
419 [ + + ]: 4340 : for (int c = 1; c < 4; c++) {
420 : 6510 : pass->fbofmt[c] = pl_find_fmt(rr->gpu, configs[i].type, c,
421 : 3255 : configs[i].depth, 0, fmt->caps);
422 [ + + ]: 3255 : pass->fbofmt[c] = PL_DEF(pass->fbofmt[c], pass->fbofmt[c+1]);
423 : : }
424 : : return;
425 : : }
426 : :
427 : 0 : PL_WARN(rr, "Found no renderable FBO format! Most features disabled");
428 : 0 : rr->errors |= PL_RENDER_ERR_FBO;
429 : : }
430 : :
431 : 1803 : static void info_callback(void *priv, const struct pl_dispatch_info *dinfo)
432 : : {
433 : : struct pass_state *pass = priv;
434 : 1803 : const struct pl_render_params *params = pass->params;
435 [ + + ]: 1803 : if (!params->info_callback)
436 : : return;
437 : :
438 : 928 : pass->info.pass = dinfo;
439 : 928 : params->info_callback(params->info_priv, &pass->info);
440 : 928 : pass->info.index++;
441 : : }
442 : :
443 : 380 : static pl_tex get_fbo(struct pass_state *pass, int w, int h, pl_fmt fmt,
444 : : int comps, pl_debug_tag debug_tag)
445 : : {
446 : 380 : pl_renderer rr = pass->rr;
447 [ - + ]: 380 : comps = PL_DEF(comps, 4);
448 [ + + ]: 380 : fmt = PL_DEF(fmt, pass->fbofmt[comps]);
449 [ - + ]: 375 : if (!fmt)
450 : : return NULL;
451 : :
452 : 380 : struct pl_tex_params params = {
453 : : .w = w,
454 : : .h = h,
455 : : .format = fmt,
456 : : .sampleable = true,
457 : : .renderable = true,
458 : 380 : .blit_src = fmt->caps & PL_FMT_CAP_BLITTABLE,
459 : 380 : .storable = fmt->caps & PL_FMT_CAP_STORABLE,
460 : : .debug_tag = debug_tag,
461 : : };
462 : :
463 : : int best_idx = -1;
464 : : int best_diff = 0;
465 : :
466 : : // Find the best-fitting texture out of rr->fbos
467 [ + + ]: 1137 : for (int i = 0; i < rr->fbos.num; i++) {
468 [ + + ]: 757 : if (pass->fbos_used[i])
469 : 147 : continue;
470 : :
471 : : // Orthogonal distance, with penalty for format mismatches
472 : 1220 : int diff = abs(rr->fbos.elem[i]->params.w - w) +
473 : 610 : abs(rr->fbos.elem[i]->params.h - h) +
474 [ + + ]: 610 : ((rr->fbos.elem[i]->params.format != fmt) ? 1000 : 0);
475 : :
476 [ + + ]: 610 : if (best_idx < 0 || diff < best_diff) {
477 : : best_idx = i;
478 : : best_diff = diff;
479 : : }
480 : : }
481 : :
482 : : // No texture found at all, add a new one
483 [ + + ]: 380 : if (best_idx < 0) {
484 : : best_idx = rr->fbos.num;
485 [ + + - + : 26 : PL_ARRAY_APPEND(rr, rr->fbos, NULL);
- + ]
486 [ + - ]: 26 : pl_grow(pass->tmp, &pass->fbos_used, rr->fbos.num * sizeof(bool));
487 : 26 : pass->fbos_used[best_idx] = false;
488 : : }
489 : :
490 [ - + ]: 380 : if (!pl_tex_recreate(rr->gpu, &rr->fbos.elem[best_idx], ¶ms))
491 : : return NULL;
492 : :
493 : 380 : pass->fbos_used[best_idx] = true;
494 : 380 : return rr->fbos.elem[best_idx];
495 : : }
496 : :
497 : : // Forcibly convert an img to `tex`, dispatching where necessary
498 : 1176 : static pl_tex _img_tex(struct pass_state *pass, struct img *img, pl_debug_tag tag)
499 : : {
500 [ + + ]: 1176 : if (img->tex) {
501 [ - + ]: 814 : pl_assert(!img->sh);
502 : : return img->tex;
503 : : }
504 : :
505 : 362 : pl_renderer rr = pass->rr;
506 : 362 : pl_tex tex = get_fbo(pass, img->w, img->h, img->fmt, img->comps, tag);
507 : 362 : img->fmt = NULL;
508 : :
509 [ - + ]: 362 : if (!tex) {
510 : 0 : PL_ERR(rr, "Failed creating FBO texture! Disabling advanced rendering..");
511 : 0 : memset(pass->fbofmt, 0, sizeof(pass->fbofmt));
512 : 0 : pl_dispatch_abort(rr->dp, &img->sh);
513 : 0 : rr->errors |= PL_RENDER_ERR_FBO;
514 : 0 : return img->err_tex;
515 : : }
516 : :
517 [ - + ]: 362 : pl_assert(img->sh);
518 : 362 : bool ok = pl_dispatch_finish(rr->dp, pl_dispatch_params(
519 : : .shader = &img->sh,
520 : : .target = tex,
521 : : ));
522 : :
523 : 362 : const char *err_msg = img->err_msg;
524 : 362 : enum pl_render_error err_enum = img->err_enum;
525 : 362 : pl_tex err_tex = img->err_tex;
526 : 362 : img->err_msg = NULL;
527 : 362 : img->err_enum = PL_RENDER_ERR_NONE;
528 : 362 : img->err_tex = NULL;
529 : :
530 [ - + ]: 362 : if (!ok) {
531 [ # # ]: 0 : PL_ERR(rr, "%s", PL_DEF(err_msg, "Failed dispatching intermediate pass!"));
532 : 0 : rr->errors |= err_enum;
533 : 0 : img->sh = pl_dispatch_begin(rr->dp);
534 : 0 : img->tex = err_tex;
535 : 0 : return img->tex;
536 : : }
537 : :
538 : 362 : img->tex = tex;
539 : 362 : return img->tex;
540 : : }
541 : :
542 : : #define img_tex(pass, img) _img_tex(pass, img, PL_DEBUG_TAG)
543 : :
544 : : // Forcibly convert an img to `sh`, sampling where necessary
545 : 6810 : static pl_shader img_sh(struct pass_state *pass, struct img *img)
546 : : {
547 [ + + ]: 6810 : if (img->sh) {
548 [ - + ]: 6786 : pl_assert(!img->tex);
549 : : return img->sh;
550 : : }
551 : :
552 [ - + ]: 24 : pl_assert(img->tex);
553 : 24 : img->sh = pl_dispatch_begin_ex(pass->rr->dp, img->unique);
554 : 24 : pl_shader_sample_direct(img->sh, pl_sample_src( .tex = img->tex ));
555 : :
556 : 24 : img->tex = NULL;
557 : 24 : return img->sh;
558 : : }
559 : :
560 : : enum sampler_type {
561 : : SAMPLER_DIRECT, // pick based on texture caps
562 : : SAMPLER_NEAREST, // direct sampling, force nearest
563 : : SAMPLER_BICUBIC, // fast bicubic scaling
564 : : SAMPLER_HERMITE, // fast hermite scaling
565 : : SAMPLER_GAUSSIAN, // fast gaussian scaling
566 : : SAMPLER_COMPLEX, // complex custom filters
567 : : SAMPLER_OVERSAMPLE,
568 : : };
569 : :
570 : : enum sampler_dir {
571 : : SAMPLER_NOOP, // 1:1 scaling
572 : : SAMPLER_UP, // upscaling
573 : : SAMPLER_DOWN, // downscaling
574 : : };
575 : :
576 : : enum sampler_usage {
577 : : SAMPLER_MAIN,
578 : : SAMPLER_PLANE,
579 : : SAMPLER_CONTRAST,
580 : : };
581 : :
582 : : struct sampler_info {
583 : : const struct pl_filter_config *config; // if applicable
584 : : enum sampler_usage usage;
585 : : enum sampler_type type;
586 : : enum sampler_dir dir;
587 : : enum sampler_dir dir_sep[2];
588 : : };
589 : :
590 : 2565 : static struct sampler_info sample_src_info(struct pass_state *pass,
591 : : const struct pl_sample_src *src,
592 : : enum sampler_usage usage)
593 : : {
594 : 2565 : const struct pl_render_params *params = pass->params;
595 : : struct sampler_info info = { .usage = usage };
596 : 2565 : pl_renderer rr = pass->rr;
597 : :
598 : 2565 : float rx = src->new_w / fabsf(pl_rect_w(src->rect));
599 [ + + ]: 2565 : if (rx < 1.0 - 1e-6) {
600 : : info.dir_sep[0] = SAMPLER_DOWN;
601 [ + + ]: 2343 : } else if (rx > 1.0 + 1e-6) {
602 : : info.dir_sep[0] = SAMPLER_UP;
603 : : }
604 : :
605 : 2565 : float ry = src->new_h / fabsf(pl_rect_h(src->rect));
606 [ + + ]: 2565 : if (ry < 1.0 - 1e-6) {
607 : : info.dir_sep[1] = SAMPLER_DOWN;
608 [ + + ]: 2343 : } else if (ry > 1.0 + 1e-6) {
609 : : info.dir_sep[1] = SAMPLER_UP;
610 : : }
611 : :
612 [ - + ]: 2565 : if (params->correct_subpixel_offsets) {
613 [ # # # # ]: 0 : if (!info.dir_sep[0] && fabsf(src->rect.x0) > 1e-6f)
614 : : info.dir_sep[0] = SAMPLER_UP;
615 [ # # # # ]: 0 : if (!info.dir_sep[1] && fabsf(src->rect.y0) > 1e-6f)
616 : : info.dir_sep[1] = SAMPLER_UP;
617 : : }
618 : :
619 : : // We use PL_MAX so downscaling overrides upscaling when choosing scalers
620 : 2565 : info.dir = PL_MAX(info.dir_sep[0], info.dir_sep[1]);
621 [ + + + ]: 2565 : switch (info.dir) {
622 : 222 : case SAMPLER_DOWN:
623 [ + - ]: 222 : if (usage == SAMPLER_CONTRAST) {
624 : : info.config = &pl_filter_bicubic;
625 [ + + + - ]: 222 : } else if (usage == SAMPLER_PLANE && params->plane_downscaler) {
626 : : info.config = params->plane_downscaler;
627 : : } else {
628 : 222 : info.config = params->downscaler;
629 : : }
630 : : break;
631 : 209 : case SAMPLER_UP:
632 [ + + + - ]: 209 : if (usage == SAMPLER_PLANE && params->plane_upscaler) {
633 : : info.config = params->plane_upscaler;
634 : : } else {
635 [ - + ]: 209 : pl_assert(usage != SAMPLER_CONTRAST);
636 : 209 : info.config = params->upscaler;
637 : : }
638 : : break;
639 : 2134 : case SAMPLER_NOOP:
640 : : info.type = SAMPLER_NEAREST;
641 : 2134 : return info;
642 : : }
643 : :
644 [ + - + + ]: 431 : if ((rr->errors & PL_RENDER_ERR_SAMPLING) || !info.config) {
645 : : info.type = SAMPLER_DIRECT;
646 [ + + ]: 408 : } else if (info.config->kernel == &pl_filter_function_oversample) {
647 : : info.type = SAMPLER_OVERSAMPLE;
648 : : } else {
649 : : info.type = SAMPLER_COMPLEX;
650 : :
651 : : // Try using faster replacements for GPU built-in scalers
652 [ + + ]: 392 : pl_fmt texfmt = src->tex ? src->tex->params.format : pass->fbofmt[4];
653 : 392 : bool can_linear = texfmt->caps & PL_FMT_CAP_LINEAR;
654 [ + + - + ]: 392 : bool can_fast = info.dir == SAMPLER_UP || params->skip_anti_aliasing;
655 : :
656 [ + - ]: 192 : if (can_fast && !params->disable_builtin_scalers) {
657 [ + - + + ]: 192 : if (can_linear && pl_filter_config_eq(info.config, &pl_filter_bicubic))
658 : : info.type = SAMPLER_BICUBIC;
659 [ + + ]: 192 : if (can_linear && pl_filter_config_eq(info.config, &pl_filter_hermite))
660 : : info.type = SAMPLER_HERMITE;
661 [ + + ]: 192 : if (can_linear && pl_filter_config_eq(info.config, &pl_filter_gaussian))
662 : : info.type = SAMPLER_GAUSSIAN;
663 [ + + ]: 192 : if (can_linear && pl_filter_config_eq(info.config, &pl_filter_bilinear))
664 : : info.type = SAMPLER_DIRECT;
665 [ + + ]: 192 : if (pl_filter_config_eq(info.config, &pl_filter_nearest))
666 : 8 : info.type = can_linear ? SAMPLER_NEAREST : SAMPLER_DIRECT;
667 : : }
668 : : }
669 : :
670 : : // Disable advanced scaling without FBOs
671 [ + - - - ]: 431 : if (!pass->fbofmt[4] && info.type == SAMPLER_COMPLEX)
672 : : info.type = SAMPLER_DIRECT;
673 : :
674 : 431 : return info;
675 : : }
676 : :
677 : 995 : static void dispatch_sampler(struct pass_state *pass, pl_shader sh,
678 : : struct sampler *sampler, enum sampler_usage usage,
679 : : pl_tex target_tex, const struct pl_sample_src *src)
680 : : {
681 : 995 : const struct pl_render_params *params = pass->params;
682 [ - + ]: 995 : if (!sampler)
683 : 0 : goto fallback;
684 : :
685 : 995 : pl_renderer rr = pass->rr;
686 : 995 : struct sampler_info info = sample_src_info(pass, src, usage);
687 : : pl_shader_obj *lut = NULL;
688 [ + + + - ]: 995 : switch (info.dir) {
689 : 780 : case SAMPLER_NOOP:
690 : 780 : goto fallback;
691 : 114 : case SAMPLER_DOWN:
692 : 114 : lut = &sampler->downscaler_state;
693 : 114 : break;
694 : 101 : case SAMPLER_UP:
695 : 101 : lut = &sampler->upscaler_state;
696 : 101 : break;
697 : : }
698 : :
699 [ + + + + : 215 : switch (info.type) {
+ + + ]
700 : 15 : case SAMPLER_DIRECT:
701 : 15 : goto fallback;
702 : 4 : case SAMPLER_NEAREST:
703 : 4 : pl_shader_sample_nearest(sh, src);
704 : 200 : return;
705 : 8 : case SAMPLER_OVERSAMPLE:
706 : 8 : pl_shader_sample_oversample(sh, src, info.config->kernel->params[0]);
707 : 8 : return;
708 : 4 : case SAMPLER_BICUBIC:
709 : 4 : pl_shader_sample_bicubic(sh, src);
710 : 4 : return;
711 : 4 : case SAMPLER_HERMITE:
712 : 4 : pl_shader_sample_hermite(sh, src);
713 : 4 : return;
714 : 4 : case SAMPLER_GAUSSIAN:
715 : 4 : pl_shader_sample_gaussian(sh, src);
716 : 4 : return;
717 : : case SAMPLER_COMPLEX:
718 : : break; // continue below
719 : : }
720 : :
721 [ - + ]: 176 : pl_assert(lut);
722 : 352 : struct pl_sample_filter_params fparams = {
723 : : .filter = *info.config,
724 : 176 : .antiring = params->antiringing_strength,
725 [ - + - - ]: 176 : .no_widening = params->skip_anti_aliasing && usage != SAMPLER_CONTRAST,
726 : : .lut = lut,
727 : : };
728 : :
729 [ - + ]: 176 : if (target_tex) {
730 : 0 : fparams.no_compute = !target_tex->params.storable;
731 : : } else {
732 : 176 : fparams.no_compute = !(pass->fbofmt[4]->caps & PL_FMT_CAP_STORABLE);
733 : : }
734 : :
735 : : bool ok;
736 [ + + ]: 176 : if (info.config->polar) {
737 : : // Polar samplers are always a single function call
738 : 72 : ok = pl_shader_sample_polar(sh, src, &fparams);
739 [ + - + - ]: 104 : } else if (info.dir_sep[0] && info.dir_sep[1]) {
740 : : // Scaling is needed in both directions
741 : 104 : struct pl_sample_src src1 = *src, src2 = *src;
742 : 104 : src1.new_w = src->tex->params.w;
743 : 104 : src1.rect.x0 = 0;
744 : 104 : src1.rect.x1 = src1.new_w;;
745 : 104 : src2.rect.y0 = 0;
746 : 104 : src2.rect.y1 = src1.new_h;
747 : :
748 : 104 : pl_shader tsh = pl_dispatch_begin(rr->dp);
749 : 104 : ok = pl_shader_sample_ortho2(tsh, &src1, &fparams);
750 [ - + ]: 104 : if (!ok) {
751 : 0 : pl_dispatch_abort(rr->dp, &tsh);
752 : 0 : goto done;
753 : : }
754 : :
755 : 104 : struct img img = {
756 : : .sh = tsh,
757 : 104 : .w = src1.new_w,
758 : 104 : .h = src1.new_h,
759 : 104 : .comps = src->components,
760 : : };
761 : :
762 : 104 : src2.tex = img_tex(pass, &img);
763 : 104 : src2.scale = 1.0;
764 [ + - + - ]: 104 : ok = src2.tex && pl_shader_sample_ortho2(sh, &src2, &fparams);
765 : : } else {
766 : : // Scaling is needed only in one direction
767 : 0 : ok = pl_shader_sample_ortho2(sh, src, &fparams);
768 : : }
769 : :
770 : 72 : done:
771 [ - + ]: 72 : if (!ok) {
772 : 0 : PL_ERR(rr, "Failed dispatching scaler.. disabling");
773 : 0 : rr->errors |= PL_RENDER_ERR_SAMPLING;
774 : 0 : goto fallback;
775 : : }
776 : :
777 : : return;
778 : :
779 : 795 : fallback:
780 : : // If all else fails, fall back to auto sampling
781 : 795 : pl_shader_sample_direct(sh, src);
782 : : }
783 : :
784 : 1103 : static void swizzle_color(pl_shader sh, int comps, const int comp_map[4],
785 : : bool force_alpha)
786 : : {
787 : 1103 : ident_t orig = sh_fresh(sh, "orig_color");
788 : 1103 : GLSL("vec4 "$" = color; \n"
789 : : "color = vec4(0.0, 0.0, 0.0, 1.0); \n", orig);
790 : :
791 : : static const int def_map[4] = {0, 1, 2, 3};
792 [ - + ]: 1103 : comp_map = PL_DEF(comp_map, def_map);
793 : :
794 [ + + ]: 4382 : for (int c = 0; c < comps; c++) {
795 [ + - ]: 3279 : if (comp_map[c] >= 0)
796 : 3279 : GLSL("color[%d] = "$"[%d]; \n", c, orig, comp_map[c]);
797 : : }
798 : :
799 [ + + ]: 1103 : if (force_alpha)
800 : 16 : GLSL("color.a = "$".a; \n", orig);
801 : 1103 : }
802 : :
803 : : // `scale` adapts from `pass->dst_rect` to the plane being rendered to
804 : 1898 : static void draw_overlays(struct pass_state *pass, pl_tex fbo,
805 : : int comps, const int comp_map[4],
806 : : const struct pl_overlay *overlays, int num,
807 : : struct pl_color_space color, struct pl_color_repr repr,
808 : : const pl_transform2x2 *output_shift)
809 : : {
810 : 1898 : pl_renderer rr = pass->rr;
811 [ + + + - ]: 1898 : if (num <= 0 || (rr->errors & PL_RENDER_ERR_OVERLAY))
812 : 1882 : return;
813 : :
814 : 16 : enum pl_fmt_caps caps = fbo->params.format->caps;
815 [ + - ]: 16 : if (!(rr->errors & PL_RENDER_ERR_BLENDING) &&
816 [ - + ]: 16 : !(caps & PL_FMT_CAP_BLENDABLE))
817 : : {
818 : 0 : PL_WARN(rr, "Trying to draw an overlay to a non-blendable target. "
819 : : "Alpha blending is disabled, results may be incorrect!");
820 : 0 : rr->errors |= PL_RENDER_ERR_BLENDING;
821 : : }
822 : :
823 [ + + ]: 16 : const struct pl_frame *image = pass->src_ref >= 0 ? &pass->image : NULL;
824 : : pl_transform2x2 src_to_dst;
825 : : if (image) {
826 : 12 : float rx = pl_rect_w(pass->dst_rect) / pl_rect_w(image->crop);
827 : 12 : float ry = pl_rect_h(pass->dst_rect) / pl_rect_h(image->crop);
828 : 12 : src_to_dst = (pl_transform2x2) {
829 : : .mat.m = {{ rx, 0 }, { 0, ry }},
830 : : .c = {
831 : 12 : pass->dst_rect.x0 - rx * image->crop.x0,
832 : 12 : pass->dst_rect.y0 - ry * image->crop.y0,
833 : : },
834 : : };
835 : :
836 [ - + ]: 12 : if (pass->rotation % PL_ROTATION_180 == PL_ROTATION_90) {
837 : 0 : PL_SWAP(src_to_dst.c[0], src_to_dst.c[1]);
838 : 0 : src_to_dst.mat = (pl_matrix2x2) {{{ 0, ry }, { rx, 0 }}};
839 : : }
840 : : }
841 : :
842 : : const struct pl_frame *target = &pass->target;
843 : 16 : pl_rect2df dst_crop = target->crop;
844 : 16 : pl_rect2df_rotate(&dst_crop, -pass->rotation);
845 : 16 : pl_rect2df_normalize(&dst_crop);
846 : :
847 [ + + ]: 32 : for (int n = 0; n < num; n++) {
848 : 16 : struct pl_overlay ol = overlays[n];
849 [ - + ]: 16 : if (!ol.num_parts)
850 : 0 : continue;
851 : :
852 [ + - ]: 16 : if (!ol.coords) {
853 : 16 : ol.coords = overlays == target->overlays
854 : : ? PL_OVERLAY_COORDS_DST_FRAME
855 [ + + ]: 16 : : PL_OVERLAY_COORDS_SRC_FRAME;
856 : : }
857 : :
858 : 16 : pl_transform2x2 tf = pl_transform2x2_identity;
859 [ - + - - : 16 : switch (ol.coords) {
+ ]
860 : 0 : case PL_OVERLAY_COORDS_SRC_CROP:
861 [ # # ]: 0 : if (!image)
862 : 0 : continue;
863 : 0 : tf.c[0] = image->crop.x0;
864 : 0 : tf.c[1] = image->crop.y0;
865 : : // fall through
866 : 8 : case PL_OVERLAY_COORDS_SRC_FRAME:
867 [ - + ]: 8 : if (!image)
868 : 0 : continue;
869 : 8 : pl_transform2x2_rmul(&src_to_dst, &tf);
870 : 8 : break;
871 : 0 : case PL_OVERLAY_COORDS_DST_CROP:
872 : 0 : tf.c[0] = dst_crop.x0;
873 : 0 : tf.c[1] = dst_crop.y0;
874 : 0 : break;
875 : : case PL_OVERLAY_COORDS_DST_FRAME:
876 : : break;
877 : : case PL_OVERLAY_COORDS_AUTO:
878 : : case PL_OVERLAY_COORDS_COUNT:
879 : 0 : pl_unreachable();
880 : : }
881 : :
882 [ + - ]: 16 : if (output_shift)
883 : 16 : pl_transform2x2_rmul(output_shift, &tf);
884 : :
885 : : // Construct vertex/index buffers
886 : 16 : rr->osd_vertices.num = 0;
887 : 16 : rr->osd_indices.num = 0;
888 [ + + ]: 40 : for (int i = 0; i < ol.num_parts; i++) {
889 : 24 : const struct pl_overlay_part *part = &ol.parts[i];
890 : :
891 : : #define EMIT_VERT(x, y) \
892 : : do { \
893 : : float pos[2] = { part->dst.x, part->dst.y }; \
894 : : pl_transform2x2_apply(&tf, pos); \
895 : : PL_ARRAY_APPEND(rr, rr->osd_vertices, (struct osd_vertex) { \
896 : : .pos = { \
897 : : 2.0 * (pos[0] / fbo->params.w) - 1.0, \
898 : : 2.0 * (pos[1] / fbo->params.h) - 1.0, \
899 : : }, \
900 : : .coord = { \
901 : : part->src.x / ol.tex->params.w, \
902 : : part->src.y / ol.tex->params.h, \
903 : : }, \
904 : : .color = { \
905 : : part->color[0], part->color[1], \
906 : : part->color[2], part->color[3], \
907 : : }, \
908 : : }); \
909 : : } while (0)
910 : :
911 : 24 : int idx_base = rr->osd_vertices.num;
912 [ + + - + : 24 : EMIT_VERT(x0, y0); // idx 0: top left
- + ]
913 [ - + - + : 24 : EMIT_VERT(x1, y0); // idx 1: top right
- + ]
914 [ - + - + : 24 : EMIT_VERT(x0, y1); // idx 2: bottom left
- + ]
915 [ - + - + : 24 : EMIT_VERT(x1, y1); // idx 3: bottom right
- + ]
916 [ + + - + : 24 : PL_ARRAY_APPEND(rr, rr->osd_indices, idx_base + 0);
- + ]
917 [ - + - + : 24 : PL_ARRAY_APPEND(rr, rr->osd_indices, idx_base + 1);
- + ]
918 [ - + - + : 24 : PL_ARRAY_APPEND(rr, rr->osd_indices, idx_base + 2);
- + ]
919 [ - + - + : 24 : PL_ARRAY_APPEND(rr, rr->osd_indices, idx_base + 2);
- + ]
920 [ - + + + : 24 : PL_ARRAY_APPEND(rr, rr->osd_indices, idx_base + 1);
- + ]
921 [ - + - + : 24 : PL_ARRAY_APPEND(rr, rr->osd_indices, idx_base + 3);
- + ]
922 : : }
923 : :
924 : : // Draw parts
925 : 16 : pl_shader sh = pl_dispatch_begin(rr->dp);
926 : 16 : ident_t tex = sh_desc(sh, (struct pl_shader_desc) {
927 : : .desc = {
928 : : .name = "osd_tex",
929 : : .type = PL_DESC_SAMPLED_TEX,
930 : : },
931 : : .binding = {
932 : 16 : .object = ol.tex,
933 : 16 : .sample_mode = (ol.tex->params.format->caps & PL_FMT_CAP_LINEAR)
934 : : ? PL_TEX_SAMPLE_LINEAR
935 : 16 : : PL_TEX_SAMPLE_NEAREST,
936 : : },
937 : : });
938 : :
939 : 16 : sh_describe(sh, "overlay");
940 : 16 : GLSL("// overlay \n");
941 : :
942 [ + + - - ]: 16 : switch (ol.mode) {
943 : : case PL_OVERLAY_NORMAL:
944 : 8 : GLSL("vec4 color = textureLod("$", coord, 0.0); \n", tex);
945 : 8 : break;
946 : : case PL_OVERLAY_MONOCHROME:
947 : 8 : GLSL("vec4 color = osd_color; \n");
948 : 8 : break;
949 : : case PL_OVERLAY_MODE_COUNT:
950 : 0 : pl_unreachable();
951 : : };
952 : :
953 : : static const struct pl_color_map_params osd_params = {
954 : : PL_COLOR_MAP_DEFAULTS
955 : : .tone_mapping_function = &pl_tone_map_linear,
956 : : .gamut_mapping = &pl_gamut_map_saturation,
957 : : };
958 : :
959 : 16 : sh->output = PL_SHADER_SIG_COLOR;
960 : 16 : pl_shader_decode_color(sh, &ol.repr, NULL);
961 [ - + ]: 16 : if (target->icc)
962 : : color.transfer = PL_COLOR_TRC_LINEAR;
963 : 16 : pl_shader_color_map_ex(sh, &osd_params, pl_color_map_args(ol.color, color));
964 [ - + ]: 16 : if (target->icc)
965 : 0 : pl_icc_encode(sh, target->icc, &rr->icc_state[ICC_TARGET]);
966 : :
967 : 16 : bool premul = repr.alpha == PL_ALPHA_PREMULTIPLIED;
968 : 16 : pl_shader_encode_color(sh, &repr);
969 [ + + ]: 16 : if (ol.mode == PL_OVERLAY_MONOCHROME) {
970 [ + - ]: 16 : GLSL("color.%s *= textureLod("$", coord, 0.0).r; \n",
971 : : premul ? "rgba" : "a", tex);
972 : : }
973 : :
974 : 16 : swizzle_color(sh, comps, comp_map, true);
975 : :
976 : 16 : struct pl_blend_params blend_params = {
977 [ + - ]: 16 : .src_rgb = premul ? PL_BLEND_ONE : PL_BLEND_SRC_ALPHA,
978 : : .src_alpha = PL_BLEND_ONE,
979 : : .dst_rgb = PL_BLEND_ONE_MINUS_SRC_ALPHA,
980 : : .dst_alpha = PL_BLEND_ONE_MINUS_SRC_ALPHA,
981 : : };
982 : :
983 [ - + + + ]: 24 : bool ok = pl_dispatch_vertex(rr->dp, pl_dispatch_vertex_params(
984 : : .shader = &sh,
985 : : .target = fbo,
986 : : .blend_params = (rr->errors & PL_RENDER_ERR_BLENDING)
987 : : ? NULL : &blend_params,
988 : : .vertex_stride = sizeof(struct osd_vertex),
989 : : .num_vertex_attribs = ol.mode == PL_OVERLAY_NORMAL ? 2 : 3,
990 : : .vertex_attribs = rr->osd_attribs,
991 : : .vertex_position_idx = 0,
992 : : .vertex_coords = PL_COORDS_NORMALIZED,
993 : : .vertex_type = PL_PRIM_TRIANGLE_LIST,
994 : : .vertex_count = rr->osd_indices.num,
995 : : .vertex_data = rr->osd_vertices.elem,
996 : : .index_data = rr->osd_indices.elem,
997 : : ));
998 : :
999 [ - + ]: 16 : if (!ok) {
1000 : 0 : PL_ERR(rr, "Failed rendering overlays!");
1001 : 0 : rr->errors |= PL_RENDER_ERR_OVERLAY;
1002 : 0 : return;
1003 : : }
1004 : : }
1005 : : }
1006 : :
1007 : 18 : static pl_tex get_hook_tex(void *priv, int width, int height)
1008 : : {
1009 : : struct pass_state *pass = priv;
1010 : :
1011 : 18 : return get_fbo(pass, width, height, NULL, 4, PL_DEBUG_TAG);
1012 : : }
1013 : :
1014 : : // Returns if any hook was applied (even if there were errors)
1015 : 6841 : static bool pass_hook(struct pass_state *pass, struct img *img,
1016 : : enum pl_hook_stage stage)
1017 : : {
1018 : 6841 : const struct pl_render_params *params = pass->params;
1019 : 6841 : pl_renderer rr = pass->rr;
1020 [ + + + + ]: 6841 : if (!pass->fbofmt[4] || !stage)
1021 : : return false;
1022 : :
1023 : : bool ret = false;
1024 : :
1025 [ + + ]: 6124 : for (int n = 0; n < params->num_hooks; n++) {
1026 : 100 : const struct pl_hook *hook = params->hooks[n];
1027 [ + + ]: 100 : if (!(hook->stages & stage))
1028 : 100 : continue;
1029 : :
1030 : : // Hopefully the list of disabled hooks is small, search linearly.
1031 [ - + ]: 19 : for (int i = 0; i < rr->disabled_hooks.num; i++) {
1032 [ # # ]: 0 : if (rr->disabled_hooks.elem[i] != hook->signature)
1033 : : continue;
1034 : 0 : PL_TRACE(rr, "Skipping hook %d (0x%"PRIx64") stage 0x%x",
1035 : : n, hook->signature, stage);
1036 : 0 : goto hook_skip;
1037 : : }
1038 : :
1039 : 19 : PL_TRACE(rr, "Dispatching hook %d (0x%"PRIx64") stage 0x%x",
1040 : : n, hook->signature, stage);
1041 : 19 : struct pl_hook_params hparams = {
1042 : 19 : .gpu = rr->gpu,
1043 : 19 : .dispatch = rr->dp,
1044 : : .get_tex = get_hook_tex,
1045 : : .priv = pass,
1046 : : .stage = stage,
1047 : : .rect = img->rect,
1048 : : .repr = img->repr,
1049 : : .color = img->color,
1050 : 19 : .orig_repr = &pass->image.repr,
1051 : 19 : .orig_color = &pass->image.color,
1052 : 19 : .components = img->comps,
1053 : : .src_rect = pass->ref_rect,
1054 : : .dst_rect = pass->dst_rect,
1055 : : };
1056 : :
1057 : : // TODO: Add some sort of `test` API function to the hooks that allows
1058 : : // us to skip having to touch the `img` state at all for no-ops
1059 : :
1060 [ + - - + ]: 19 : switch (hook->input) {
1061 : : case PL_HOOK_SIG_NONE:
1062 : : break;
1063 : :
1064 : 14 : case PL_HOOK_SIG_TEX: {
1065 : 14 : hparams.tex = img_tex(pass, img);
1066 [ - + ]: 14 : if (!hparams.tex) {
1067 : 0 : PL_ERR(rr, "Failed dispatching shader prior to hook!");
1068 : 0 : goto hook_error;
1069 : : }
1070 : : break;
1071 : : }
1072 : :
1073 : 0 : case PL_HOOK_SIG_COLOR:
1074 : 0 : hparams.sh = img_sh(pass, img);
1075 : 0 : break;
1076 : :
1077 : : case PL_HOOK_SIG_COUNT:
1078 : 0 : pl_unreachable();
1079 : : }
1080 : :
1081 : 19 : struct pl_hook_res res = hook->hook(hook->priv, &hparams);
1082 [ - + ]: 19 : if (res.failed) {
1083 : 0 : PL_ERR(rr, "Failed executing hook, disabling");
1084 : 0 : goto hook_error;
1085 : : }
1086 : :
1087 : 19 : bool resizable = pl_hook_stage_resizable(stage);
1088 [ + + - - ]: 19 : switch (res.output) {
1089 : : case PL_HOOK_SIG_NONE:
1090 : : break;
1091 : :
1092 : 10 : case PL_HOOK_SIG_TEX:
1093 [ - + ]: 10 : if (!resizable) {
1094 [ # # ]: 0 : if (res.tex->params.w != img->w ||
1095 : 0 : res.tex->params.h != img->h ||
1096 [ # # # # : 0 : !pl_rect2d_eq(res.rect, img->rect))
# # # # ]
1097 : : {
1098 : 0 : PL_ERR(rr, "User hook tried resizing non-resizable stage!");
1099 : 0 : goto hook_error;
1100 : : }
1101 : : }
1102 : :
1103 : 10 : *img = (struct img) {
1104 : : .tex = res.tex,
1105 : : .repr = res.repr,
1106 : : .color = res.color,
1107 : : .comps = res.components,
1108 : : .rect = res.rect,
1109 : 10 : .w = res.tex->params.w,
1110 : 10 : .h = res.tex->params.h,
1111 : 10 : .unique = img->unique,
1112 : : };
1113 : 10 : break;
1114 : :
1115 : 0 : case PL_HOOK_SIG_COLOR:
1116 [ # # ]: 0 : if (!resizable) {
1117 [ # # ]: 0 : if (res.sh->output_w != img->w ||
1118 [ # # ]: 0 : res.sh->output_h != img->h ||
1119 [ # # # # : 0 : !pl_rect2d_eq(res.rect, img->rect))
# # # # ]
1120 : : {
1121 : 0 : PL_ERR(rr, "User hook tried resizing non-resizable stage!");
1122 : 0 : goto hook_error;
1123 : : }
1124 : : }
1125 : :
1126 : 0 : *img = (struct img) {
1127 : : .sh = res.sh,
1128 : : .repr = res.repr,
1129 : : .color = res.color,
1130 : : .comps = res.components,
1131 : : .rect = res.rect,
1132 : 0 : .w = res.sh->output_w,
1133 : 0 : .h = res.sh->output_h,
1134 : 0 : .unique = img->unique,
1135 : : .err_enum = PL_RENDER_ERR_HOOKS,
1136 : : .err_msg = "Failed applying user hook",
1137 : 0 : .err_tex = hparams.tex, // if any
1138 : : };
1139 : 0 : break;
1140 : :
1141 : : case PL_HOOK_SIG_COUNT:
1142 : 0 : pl_unreachable();
1143 : : }
1144 : :
1145 : : // a hook was performed successfully
1146 : : ret = true;
1147 : :
1148 : 19 : hook_skip:
1149 : 19 : continue;
1150 : 0 : hook_error:
1151 [ # # # # : 0 : PL_ARRAY_APPEND(rr, rr->disabled_hooks, hook->signature);
# # ]
1152 : 0 : rr->errors |= PL_RENDER_ERR_HOOKS;
1153 : : }
1154 : :
1155 : : // Make sure the state remains as valid as possible, even if the resulting
1156 : : // shaders might end up nonsensical, to prevent segfaults
1157 [ + + + - ]: 6024 : if (!img->tex && !img->sh)
1158 : 0 : img->sh = pl_dispatch_begin(rr->dp);
1159 : : return ret;
1160 : : }
1161 : :
1162 : 793 : static void hdr_update_peak(struct pass_state *pass)
1163 : : {
1164 : 793 : const struct pl_render_params *params = pass->params;
1165 : 793 : pl_renderer rr = pass->rr;
1166 [ + + + + ]: 793 : if (!params->peak_detect_params || !pl_color_space_is_hdr(&pass->img.color))
1167 : 771 : goto cleanup;
1168 : :
1169 [ - + ]: 22 : if (rr->errors & PL_RENDER_ERR_PEAK_DETECT)
1170 : 0 : goto cleanup;
1171 : :
1172 [ + - + + ]: 22 : if (pass->fbofmt[4] && !(pass->fbofmt[4]->caps & PL_FMT_CAP_STORABLE))
1173 : 10 : goto cleanup;
1174 : :
1175 [ - + ]: 12 : if (!rr->gpu->limits.max_ssbo_size)
1176 : 0 : goto cleanup;
1177 : :
1178 : 12 : float max_peak = pl_color_transfer_nominal_peak(pass->img.color.transfer) *
1179 : : PL_COLOR_SDR_WHITE;
1180 [ - + ]: 12 : if (pass->img.color.transfer == PL_COLOR_TRC_HLG)
1181 : 0 : max_peak = pass->img.color.hdr.max_luma;
1182 [ - + ]: 12 : if (max_peak <= pass->target.color.hdr.max_luma + 1e-6)
1183 : 0 : goto cleanup; // no adaptation needed
1184 : :
1185 [ - + ]: 12 : if (pass->img.color.hdr.avg_pq_y)
1186 : 0 : goto cleanup; // DV metadata already present
1187 : :
1188 : : enum pl_hdr_metadata_type metadata = PL_HDR_METADATA_ANY;
1189 [ + - ]: 12 : if (params->color_map_params)
1190 : 12 : metadata = params->color_map_params->metadata;
1191 : :
1192 [ - + ]: 12 : if (metadata && metadata != PL_HDR_METADATA_CIE_Y)
1193 : 0 : goto cleanup; // metadata will be unused
1194 : :
1195 : : const struct pl_color_map_params *cpars = params->color_map_params;
1196 [ + - - + ]: 12 : bool uses_ootf = cpars && cpars->tone_mapping_function == &pl_tone_map_st2094_40;
1197 [ # # ]: 0 : if (uses_ootf && pass->img.color.hdr.ootf.num_anchors)
1198 : 0 : goto cleanup; // HDR10+ OOTF is being used
1199 : :
1200 [ - + - - ]: 12 : if (params->lut && params->lut_type == PL_LUT_CONVERSION)
1201 : 0 : goto cleanup; // LUT handles tone mapping
1202 : :
1203 [ - + - - ]: 12 : if (!pass->fbofmt[4] && !params->peak_detect_params->allow_delayed) {
1204 : 0 : PL_WARN(rr, "Disabling peak detection because "
1205 : : "`pl_peak_detect_params.allow_delayed` is false, but lack of "
1206 : : "FBOs forces the result to be delayed.");
1207 : 0 : rr->errors |= PL_RENDER_ERR_PEAK_DETECT;
1208 : 0 : goto cleanup;
1209 : : }
1210 : :
1211 : 12 : bool ok = pl_shader_detect_peak(img_sh(pass, &pass->img), pass->img.color,
1212 : 12 : &rr->tone_map_state, params->peak_detect_params);
1213 [ - + ]: 12 : if (!ok) {
1214 : 0 : PL_WARN(rr, "Failed creating HDR peak detection shader.. disabling");
1215 : 0 : rr->errors |= PL_RENDER_ERR_PEAK_DETECT;
1216 : 0 : goto cleanup;
1217 : : }
1218 : :
1219 : 12 : pass->need_peak_fbo = !params->peak_detect_params->allow_delayed;
1220 : 12 : return;
1221 : :
1222 : 781 : cleanup:
1223 : : // No peak detection required or supported, so clean up the state to avoid
1224 : : // confusing it with later frames where peak detection is enabled again
1225 : 781 : pl_reset_detected_peak(rr->tone_map_state);
1226 : : }
1227 : :
1228 : 0 : bool pl_renderer_get_hdr_metadata(pl_renderer rr,
1229 : : struct pl_hdr_metadata *metadata)
1230 : : {
1231 : 0 : return pl_get_detected_hdr_metadata(rr->tone_map_state, metadata);
1232 : : }
1233 : :
1234 : : struct plane_state {
1235 : : enum plane_type type;
1236 : : struct pl_plane plane;
1237 : : struct img img; // for per-plane shaders
1238 : : pl_fmt fmt; // per-plane format after merge
1239 : : float plane_w, plane_h; // logical plane dimensions
1240 : : };
1241 : :
1242 : : static const char *plane_type_names[] = {
1243 : : [PLANE_INVALID] = "invalid",
1244 : : [PLANE_ALPHA] = "alpha",
1245 : : [PLANE_CHROMA] = "chroma",
1246 : : [PLANE_LUMA] = "luma",
1247 : : [PLANE_RGB] = "rgb",
1248 : : [PLANE_XYZ] = "xyz",
1249 : : };
1250 : :
1251 : 829 : static void log_plane_info(pl_renderer rr, const struct plane_state *st)
1252 : : {
1253 : : const struct pl_plane *plane = &st->plane;
1254 : 829 : PL_TRACE(rr, " Type: %s", plane_type_names[st->type]);
1255 : :
1256 [ - + + - : 829 : switch (plane->components) {
- - ]
1257 : 0 : case 0:
1258 : 0 : PL_TRACE(rr, " Components: (none)");
1259 : 0 : break;
1260 : 819 : case 1:
1261 : 819 : PL_TRACE(rr, " Components: {%d}",
1262 : : plane->component_mapping[0]);
1263 : 819 : break;
1264 : 10 : case 2:
1265 : 10 : PL_TRACE(rr, " Components: {%d %d}",
1266 : : plane->component_mapping[0],
1267 : : plane->component_mapping[1]);
1268 : 10 : break;
1269 : 0 : case 3:
1270 : 0 : PL_TRACE(rr, " Components: {%d %d %d}",
1271 : : plane->component_mapping[0],
1272 : : plane->component_mapping[1],
1273 : : plane->component_mapping[2]);
1274 : 0 : break;
1275 : 0 : case 4:
1276 : 0 : PL_TRACE(rr, " Components: {%d %d %d %d}",
1277 : : plane->component_mapping[0],
1278 : : plane->component_mapping[1],
1279 : : plane->component_mapping[2],
1280 : : plane->component_mapping[3]);
1281 : 0 : break;
1282 : : }
1283 : :
1284 : 829 : PL_TRACE(rr, " Rect: {%f %f} -> {%f %f}",
1285 : : st->img.rect.x0, st->img.rect.y0, st->img.rect.x1, st->img.rect.y1);
1286 : :
1287 : 829 : PL_TRACE(rr, " Bits: %d (used) / %d (sampled), shift %d",
1288 : : st->img.repr.bits.color_depth,
1289 : : st->img.repr.bits.sample_depth,
1290 : : st->img.repr.bits.bit_shift);
1291 : 829 : }
1292 : :
1293 : : // Returns true if debanding was applied
1294 : 802 : static bool plane_deband(struct pass_state *pass, struct img *img, float neutral[3])
1295 : : {
1296 : 802 : const struct pl_render_params *params = pass->params;
1297 : : const struct pl_frame *image = &pass->image;
1298 : 802 : pl_renderer rr = pass->rr;
1299 [ + - ]: 802 : if ((rr->errors & PL_RENDER_ERR_DEBANDING) ||
1300 [ + + + - ]: 802 : !params->deband_params || !pass->fbofmt[4])
1301 : : {
1302 : : return false;
1303 : : }
1304 : :
1305 : 16 : struct pl_color_repr repr = img->repr;
1306 : 48 : struct pl_sample_src src = {
1307 : 16 : .tex = img_tex(pass, img),
1308 : 16 : .components = img->comps,
1309 : 16 : .scale = pl_color_repr_normalize(&repr),
1310 : : };
1311 : :
1312 : : // Divide the deband grain scale by the effective current colorspace nominal
1313 : : // peak, to make sure the output intensity of the grain is as independent
1314 : : // of the source as possible, even though it happens this early in the
1315 : : // process (well before any linearization / output adaptation)
1316 : 16 : struct pl_deband_params dparams = *params->deband_params;
1317 : 16 : dparams.grain /= image->color.hdr.max_luma / PL_COLOR_SDR_WHITE;
1318 : : memcpy(dparams.grain_neutral, neutral, sizeof(dparams.grain_neutral));
1319 : :
1320 : 16 : img->tex = NULL;
1321 : 16 : img->sh = pl_dispatch_begin_ex(rr->dp, true);
1322 : 16 : pl_shader_deband(img->sh, &src, &dparams);
1323 : 16 : img->err_msg = "Failed applying debanding... disabling!";
1324 : 16 : img->err_enum = PL_RENDER_ERR_DEBANDING;
1325 : 16 : img->err_tex = src.tex;
1326 : 16 : img->repr = repr;
1327 : 16 : return true;
1328 : : }
1329 : :
1330 : : // Returns true if grain was applied
1331 : 802 : static bool plane_film_grain(struct pass_state *pass, int plane_idx,
1332 : : struct plane_state *st,
1333 : : const struct plane_state *ref)
1334 : : {
1335 : : const struct pl_frame *image = &pass->image;
1336 : 802 : pl_renderer rr = pass->rr;
1337 [ - + ]: 802 : if (rr->errors & PL_RENDER_ERR_FILM_GRAIN)
1338 : : return false;
1339 : :
1340 : 802 : struct img *img = &st->img;
1341 : : struct pl_plane *plane = &st->plane;
1342 : 802 : struct pl_color_repr repr = image->repr;
1343 : 802 : bool is_orig_repr = pl_color_repr_equal(&st->img.repr, &image->repr);
1344 [ - + ]: 802 : if (!is_orig_repr) {
1345 : : // Propagate the original color depth to the film grain algorithm, but
1346 : : // update the sample depth and effective bit shift based on the state
1347 : : // of the current texture, which is guaranteed to already be
1348 : : // normalized.
1349 [ # # ]: 0 : pl_assert(st->img.repr.bits.bit_shift == 0);
1350 : 0 : repr.bits.sample_depth = st->img.repr.bits.sample_depth;
1351 : 0 : repr.bits.bit_shift = repr.bits.sample_depth - repr.bits.color_depth;
1352 : : }
1353 : :
1354 : 802 : struct pl_film_grain_params grain_params = {
1355 : : .data = image->film_grain,
1356 : 802 : .luma_tex = ref->plane.texture,
1357 : : .repr = &repr,
1358 : 802 : .components = plane->components,
1359 : : };
1360 : :
1361 [ + + + - ]: 802 : switch (image->film_grain.type) {
1362 : : case PL_FILM_GRAIN_NONE: return false;
1363 : : case PL_FILM_GRAIN_H274: break;
1364 : : case PL_FILM_GRAIN_AV1:
1365 : : grain_params.luma_tex = ref->plane.texture;
1366 [ + + ]: 8 : for (int c = 0; c < ref->plane.components; c++) {
1367 [ + - ]: 4 : if (ref->plane.component_mapping[c] == PL_CHANNEL_Y)
1368 : 4 : grain_params.luma_comp = c;
1369 : : }
1370 : : break;
1371 : 0 : default: pl_unreachable();
1372 : : }
1373 : :
1374 [ + + ]: 16 : for (int c = 0; c < plane->components; c++)
1375 : 8 : grain_params.component_mapping[c] = plane->component_mapping[c];
1376 : :
1377 [ - + ]: 8 : if (!pl_needs_film_grain(&grain_params))
1378 : : return false;
1379 : :
1380 [ - + ]: 8 : if (!pass->fbofmt[plane->components]) {
1381 : 0 : PL_ERR(rr, "Film grain required but no renderable format available.. "
1382 : : "disabling!");
1383 : 0 : rr->errors |= PL_RENDER_ERR_FILM_GRAIN;
1384 : 0 : return false;
1385 : : }
1386 : :
1387 : 8 : grain_params.tex = img_tex(pass, img);
1388 [ - + ]: 8 : if (!grain_params.tex)
1389 : : return false;
1390 : :
1391 : 8 : img->sh = pl_dispatch_begin_ex(rr->dp, true);
1392 [ + + ]: 8 : if (!pl_shader_film_grain(img->sh, &rr->grain_state[plane_idx], &grain_params)) {
1393 : 2 : pl_dispatch_abort(rr->dp, &img->sh);
1394 : 2 : rr->errors |= PL_RENDER_ERR_FILM_GRAIN;
1395 : 2 : return false;
1396 : : }
1397 : :
1398 : 6 : img->tex = NULL;
1399 : 6 : img->err_msg = "Failed applying film grain.. disabling!";
1400 : 6 : img->err_enum = PL_RENDER_ERR_FILM_GRAIN;
1401 : 6 : img->err_tex = grain_params.tex;
1402 [ + - ]: 6 : if (is_orig_repr)
1403 : 6 : img->repr = repr;
1404 : : return true;
1405 : : }
1406 : :
1407 : : static const enum pl_hook_stage plane_hook_stages[] = {
1408 : : [PLANE_ALPHA] = PL_HOOK_ALPHA_INPUT,
1409 : : [PLANE_CHROMA] = PL_HOOK_CHROMA_INPUT,
1410 : : [PLANE_LUMA] = PL_HOOK_LUMA_INPUT,
1411 : : [PLANE_RGB] = PL_HOOK_RGB_INPUT,
1412 : : [PLANE_XYZ] = PL_HOOK_XYZ_INPUT,
1413 : : };
1414 : :
1415 : : static const enum pl_hook_stage plane_scaled_hook_stages[] = {
1416 : : [PLANE_ALPHA] = PL_HOOK_ALPHA_SCALED,
1417 : : [PLANE_CHROMA] = PL_HOOK_CHROMA_SCALED,
1418 : : [PLANE_LUMA] = 0, // never hooked
1419 : : [PLANE_RGB] = 0,
1420 : : [PLANE_XYZ] = 0,
1421 : : };
1422 : :
1423 : : static enum pl_lut_type guess_frame_lut_type(const struct pl_frame *frame,
1424 : : bool reversed)
1425 : : {
1426 [ + + + + : 2671 : if (!frame->lut)
+ + ]
1427 : : return PL_LUT_UNKNOWN;
1428 [ + + + + : 96 : if (frame->lut_type)
+ + ]
1429 : : return frame->lut_type;
1430 : :
1431 : 24 : enum pl_color_system sys_in = frame->lut->repr_in.sys;
1432 : 24 : enum pl_color_system sys_out = frame->lut->repr_out.sys;
1433 : : if (reversed)
1434 : : PL_SWAP(sys_in, sys_out);
1435 : :
1436 [ + - + - : 24 : if (sys_in == PL_COLOR_SYSTEM_RGB && sys_out == sys_in)
+ - ]
1437 : : return PL_LUT_NORMALIZED;
1438 : :
1439 [ - + - - : 24 : if (sys_in == frame->repr.sys && sys_out == PL_COLOR_SYSTEM_RGB)
+ - - - +
- - - ]
1440 : : return PL_LUT_CONVERSION;
1441 : :
1442 : : // Unknown, just fall back to the default
1443 : : return PL_LUT_NATIVE;
1444 : : }
1445 : :
1446 : 5 : static pl_fmt merge_fmt(struct pass_state *pass, const struct img *a,
1447 : : const struct img *b)
1448 : : {
1449 : 5 : pl_renderer rr = pass->rr;
1450 [ + - - - ]: 5 : pl_fmt fmta = a->tex ? a->tex->params.format : PL_DEF(a->fmt, pass->fbofmt[a->comps]);
1451 [ + - - - ]: 5 : pl_fmt fmtb = b->tex ? b->tex->params.format : PL_DEF(b->fmt, pass->fbofmt[b->comps]);
1452 [ - + ]: 5 : pl_assert(fmta && fmtb);
1453 [ + - ]: 5 : if (fmta->type != fmtb->type)
1454 : : return NULL;
1455 : :
1456 : 5 : int num_comps = PL_MIN(4, a->comps + b->comps);
1457 : 5 : int min_depth = PL_MAX(a->repr.bits.sample_depth, b->repr.bits.sample_depth);
1458 : :
1459 : : // Only return formats that support all relevant caps of both formats
1460 : : const enum pl_fmt_caps mask = PL_FMT_CAP_SAMPLEABLE | PL_FMT_CAP_LINEAR;
1461 : 5 : enum pl_fmt_caps req_caps = (fmta->caps & mask) | (fmtb->caps & mask);
1462 : :
1463 : 5 : return pl_find_fmt(rr->gpu, fmta->type, num_comps, min_depth, 0, req_caps);
1464 : : }
1465 : :
1466 : : // Applies a series of rough heuristics to figure out whether we expect any
1467 : : // performance gains from plane merging. This is basically a series of checks
1468 : : // for operations that we *know* benefit from merged planes
1469 : 802 : static bool want_merge(struct pass_state *pass,
1470 : : const struct plane_state *st,
1471 : : const struct plane_state *ref)
1472 : : {
1473 : 802 : const struct pl_render_params *params = pass->params;
1474 : 802 : const pl_renderer rr = pass->rr;
1475 [ + + ]: 802 : if (!pass->fbofmt[4])
1476 : : return false;
1477 : :
1478 : : // Debanding
1479 [ + - + + ]: 798 : if (!(rr->errors & PL_RENDER_ERR_DEBANDING) && params->deband_params)
1480 : : return true;
1481 : :
1482 : : // Other plane hooks, which are generally nontrivial
1483 : 782 : enum pl_hook_stage stage = plane_hook_stages[st->type];
1484 [ + + ]: 797 : for (int i = 0; i < params->num_hooks; i++) {
1485 [ + + ]: 20 : if (params->hooks[i]->stages & stage)
1486 : : return true;
1487 : : }
1488 : :
1489 : : // Non-trivial scaling
1490 : 777 : struct pl_sample_src src = {
1491 : 777 : .new_w = ref->img.w,
1492 : 777 : .new_h = ref->img.h,
1493 : : .rect = {
1494 : 777 : .x1 = st->img.w,
1495 : 777 : .y1 = st->img.h,
1496 : : },
1497 : : };
1498 : :
1499 : 777 : struct sampler_info info = sample_src_info(pass, &src, SAMPLER_PLANE);
1500 [ - + ]: 777 : if (info.type == SAMPLER_COMPLEX)
1501 : : return true;
1502 : :
1503 : : // Film grain synthesis, can be merged for compatible channels, saving on
1504 : : // redundant sampling of the grain/offset textures
1505 : 777 : struct pl_film_grain_params grain_params = {
1506 : : .data = pass->image.film_grain,
1507 : 777 : .repr = (struct pl_color_repr *) &st->img.repr,
1508 : 777 : .components = st->plane.components,
1509 : : };
1510 : :
1511 [ + + ]: 1554 : for (int c = 0; c < st->plane.components; c++)
1512 : 777 : grain_params.component_mapping[c] = st->plane.component_mapping[c];
1513 : :
1514 [ - + + + ]: 1554 : if (!(rr->errors & PL_RENDER_ERR_FILM_GRAIN) &&
1515 : 777 : pl_needs_film_grain(&grain_params))
1516 : : {
1517 : : return true;
1518 : : }
1519 : :
1520 : : return false;
1521 : : }
1522 : :
1523 : : // This scales and merges all of the source images, and initializes pass->img.
1524 : 797 : static bool pass_read_image(struct pass_state *pass)
1525 : : {
1526 : 797 : const struct pl_render_params *params = pass->params;
1527 : : struct pl_frame *image = &pass->image;
1528 : 797 : pl_renderer rr = pass->rr;
1529 : :
1530 : : struct plane_state planes[4];
1531 : 797 : struct plane_state *ref = &planes[pass->src_ref];
1532 [ + - - + ]: 797 : pl_assert(pass->src_ref >= 0 && pass->src_ref < image->num_planes);
1533 : :
1534 [ + + ]: 1604 : for (int i = 0; i < image->num_planes; i++) {
1535 : 807 : planes[i] = (struct plane_state) {
1536 : 807 : .type = detect_plane_type(&image->planes[i], &image->repr),
1537 : : .plane = image->planes[i],
1538 : : .img = {
1539 : 807 : .w = image->planes[i].texture->params.w,
1540 : 807 : .h = image->planes[i].texture->params.h,
1541 : : .tex = image->planes[i].texture,
1542 : : .repr = image->repr,
1543 : : .color = image->color,
1544 : 807 : .comps = image->planes[i].components,
1545 : : },
1546 : 807 : .fmt = image->planes[i].texture->params.format,
1547 : : };
1548 : :
1549 : : // Explicitly skip alpha channel when overridden
1550 [ + - ]: 807 : if (image->repr.alpha == PL_ALPHA_NONE) {
1551 [ - + ]: 807 : if (planes[i].type == PLANE_ALPHA) {
1552 : 0 : planes[i].type = PLANE_INVALID;
1553 : : continue;
1554 : : } else {
1555 [ + + ]: 1614 : for (int j = 0; j < planes[i].plane.components; j++) {
1556 [ - + ]: 807 : if (planes[i].plane.component_mapping[j] == PL_CHANNEL_A)
1557 : 0 : planes[i].plane.component_mapping[j] = PL_CHANNEL_NONE;
1558 : : }
1559 : : }
1560 : : }
1561 : :
1562 : : // Deinterlace plane if needed
1563 [ + + - + ]: 807 : if (image->field != PL_FIELD_NONE && params->deinterlace_params &&
1564 [ # # # # ]: 0 : pass->fbofmt[4] && !(rr->errors & PL_RENDER_ERR_DEINTERLACING))
1565 : : {
1566 : : struct img *img = &planes[i].img;
1567 : 0 : struct pl_deinterlace_source src = {
1568 : : .cur.top = img->tex,
1569 [ # # ]: 0 : .prev.top = image->prev ? image->prev->planes[i].texture : NULL,
1570 [ # # ]: 0 : .next.top = image->next ? image->next->planes[i].texture : NULL,
1571 : : .field = image->field,
1572 : 0 : .first_field = image->first_field,
1573 : 0 : .component_mask = (1 << img->comps) - 1,
1574 : : };
1575 : :
1576 : 0 : img->tex = NULL;
1577 : 0 : img->sh = pl_dispatch_begin_ex(pass->rr->dp, true);
1578 : 0 : pl_shader_deinterlace(img->sh, &src, params->deinterlace_params);
1579 : 0 : img->err_msg = "Failed deinterlacing plane.. disabling!";
1580 : 0 : img->err_enum = PL_RENDER_ERR_DEINTERLACING;
1581 : 0 : img->err_tex = planes[i].plane.texture;
1582 : : }
1583 : : }
1584 : :
1585 : : // Original ref texture, even after preprocessing
1586 : 797 : pl_tex ref_tex = ref->plane.texture;
1587 : :
1588 : : // Merge all compatible planes into 'combined' shaders
1589 [ + + ]: 1604 : for (int i = 0; i < image->num_planes; i++) {
1590 : 807 : struct plane_state *sti = &planes[i];
1591 [ + + ]: 807 : if (!sti->type)
1592 : 5 : continue;
1593 [ + + ]: 802 : if (!want_merge(pass, sti, ref))
1594 : 773 : continue;
1595 : :
1596 : : bool did_merge = false;
1597 [ + + ]: 34 : for (int j = i+1; j < image->num_planes; j++) {
1598 : : struct plane_state *stj = &planes[j];
1599 : 5 : bool merge = sti->type == stj->type &&
1600 [ + - ]: 5 : sti->img.w == stj->img.w &&
1601 : 5 : sti->img.h == stj->img.h &&
1602 [ + - + - ]: 10 : sti->plane.shift_x == stj->plane.shift_x &&
1603 [ - + ]: 5 : sti->plane.shift_y == stj->plane.shift_y;
1604 [ - + ]: 5 : if (!merge)
1605 : 0 : continue;
1606 : :
1607 : 5 : pl_fmt fmt = merge_fmt(pass, &sti->img, &stj->img);
1608 [ - + ]: 5 : if (!fmt)
1609 : 0 : continue;
1610 : :
1611 : 5 : PL_TRACE(rr, "Merging plane %d into plane %d", j, i);
1612 : 5 : pl_shader sh = sti->img.sh;
1613 [ + - ]: 5 : if (!sh) {
1614 : 5 : sh = sti->img.sh = pl_dispatch_begin_ex(pass->rr->dp, true);
1615 : 5 : pl_shader_sample_direct(sh, pl_sample_src( .tex = sti->img.tex ));
1616 : 5 : sti->img.tex = NULL;
1617 : : }
1618 : :
1619 : 5 : pl_shader psh = NULL;
1620 [ + - ]: 5 : if (!stj->img.sh) {
1621 : 5 : psh = pl_dispatch_begin_ex(pass->rr->dp, true);
1622 : 5 : pl_shader_sample_direct(psh, pl_sample_src( .tex = stj->img.tex ));
1623 : : }
1624 : :
1625 [ - + ]: 5 : ident_t sub = sh_subpass(sh, psh ? psh : stj->img.sh);
1626 : 5 : pl_dispatch_abort(rr->dp, &psh);
1627 [ + - ]: 5 : if (!sub)
1628 : : break; // skip merging
1629 : :
1630 : 5 : sh_describe(sh, "merging planes");
1631 : 5 : GLSL("{ \n"
1632 : : "vec4 tmp = "$"(); \n", sub);
1633 [ + + ]: 10 : for (int jc = 0; jc < stj->img.comps; jc++) {
1634 : 5 : int map = stj->plane.component_mapping[jc];
1635 [ - + ]: 5 : if (map == PL_CHANNEL_NONE)
1636 : 0 : continue;
1637 : 5 : int ic = sti->img.comps++;
1638 [ - + ]: 5 : pl_assert(ic < 4);
1639 : 5 : GLSL("color[%d] = tmp[%d]; \n", ic, jc);
1640 : 5 : sti->plane.components = sti->img.comps;
1641 : 5 : sti->plane.component_mapping[ic] = map;
1642 : : }
1643 : 5 : GLSL("} \n");
1644 : :
1645 : 5 : sti->img.fmt = fmt;
1646 : 5 : sti->fmt = fmt;
1647 : 5 : pl_dispatch_abort(rr->dp, &stj->img.sh);
1648 : 5 : *stj = (struct plane_state) {0};
1649 : : did_merge = true;
1650 : : }
1651 : :
1652 [ + + ]: 29 : if (!did_merge)
1653 : 24 : continue;
1654 : :
1655 [ - + ]: 5 : if (!img_tex(pass, &sti->img)) {
1656 : 0 : PL_ERR(rr, "Failed dispatching plane merging shader, disabling FBOs!");
1657 : 0 : memset(pass->fbofmt, 0, sizeof(pass->fbofmt));
1658 : 0 : rr->errors |= PL_RENDER_ERR_FBO;
1659 : 0 : return false;
1660 : : }
1661 : : }
1662 : :
1663 : 797 : int bits = image->repr.bits.sample_depth;
1664 [ + + ]: 797 : float out_scale = bits ? (1llu << bits) / ((1llu << bits) - 1.0f) : 1.0f;
1665 : 797 : float neutral_luma = 0.0, neutral_chroma = 0.5f * out_scale;
1666 [ + + ]: 797 : if (pl_color_levels_guess(&image->repr) == PL_COLOR_LEVELS_LIMITED)
1667 : 5 : neutral_luma = 16 / 256.0f * out_scale;
1668 [ - + ]: 797 : if (!pl_color_system_is_ycbcr_like(image->repr.sys))
1669 : : neutral_chroma = neutral_luma;
1670 : :
1671 : : // Compute the sampling rc of each plane
1672 [ + + ]: 1604 : for (int i = 0; i < image->num_planes; i++) {
1673 : 807 : struct plane_state *st = &planes[i];
1674 [ + + ]: 807 : if (!st->type)
1675 : 5 : continue;
1676 : :
1677 : 802 : float rx = (float) st->plane.texture->params.w / ref_tex->params.w,
1678 : 802 : ry = (float) st->plane.texture->params.h / ref_tex->params.h;
1679 : :
1680 : : // Only accept integer scaling ratios. This accounts for the fact that
1681 : : // fractionally subsampled planes get rounded up to the nearest integer
1682 : : // size, which we want to discard.
1683 [ + + ]: 802 : float rrx = rx >= 1 ? roundf(rx) : 1.0 / roundf(1.0 / rx),
1684 [ + + ]: 802 : rry = ry >= 1 ? roundf(ry) : 1.0 / roundf(1.0 / ry);
1685 : :
1686 : 802 : float sx = st->plane.shift_x,
1687 : 802 : sy = st->plane.shift_y;
1688 : :
1689 : 802 : st->img.rect = (pl_rect2df) {
1690 : 802 : .x0 = (image->crop.x0 - sx) * rrx,
1691 : 802 : .y0 = (image->crop.y0 - sy) * rry,
1692 : 802 : .x1 = (image->crop.x1 - sx) * rrx,
1693 : 802 : .y1 = (image->crop.y1 - sy) * rry,
1694 : : };
1695 : :
1696 : 802 : st->plane_w = ref_tex->params.w * rrx;
1697 : 802 : st->plane_h = ref_tex->params.h * rry;
1698 : :
1699 : 802 : PL_TRACE(rr, "Plane %d:", i);
1700 : 802 : log_plane_info(rr, st);
1701 : :
1702 : 802 : float neutral[3] = {0.0};
1703 [ + + ]: 1609 : for (int c = 0, idx = 0; c < st->plane.components; c++) {
1704 [ + + - ]: 807 : switch (st->plane.component_mapping[c]) {
1705 : 797 : case PL_CHANNEL_Y: neutral[idx++] = neutral_luma; break;
1706 : 10 : case PL_CHANNEL_U: // fall through
1707 : 10 : case PL_CHANNEL_V: neutral[idx++] = neutral_chroma; break;
1708 : : }
1709 : : }
1710 : :
1711 : : // The order of operations (deband -> film grain -> user hooks) is
1712 : : // chosen to maximize quality. Note that film grain requires unmodified
1713 : : // plane sizes, so it has to be before user hooks. As for debanding,
1714 : : // it's reduced in quality after e.g. plane scalers as well. It's also
1715 : : // made less effective by performing film grain synthesis first.
1716 : :
1717 [ + + ]: 802 : if (plane_deband(pass, &st->img, neutral)) {
1718 : 16 : PL_TRACE(rr, "After debanding:");
1719 : 16 : log_plane_info(rr, st);
1720 : : }
1721 : :
1722 [ + + ]: 802 : if (plane_film_grain(pass, i, st, ref)) {
1723 : 6 : PL_TRACE(rr, "After film grain:");
1724 : 6 : log_plane_info(rr, st);
1725 : : }
1726 : :
1727 [ + + ]: 802 : if (pass_hook(pass, &st->img, plane_hook_stages[st->type])) {
1728 : 5 : st->plane.texture = st->img.tex;
1729 : 5 : st->plane_w = st->img.w;
1730 : 5 : st->plane_h = st->img.h;
1731 : 5 : PL_TRACE(rr, "After user hooks:");
1732 : 5 : log_plane_info(rr, st);
1733 : : }
1734 : : }
1735 : :
1736 : 797 : pl_shader sh = pl_dispatch_begin_ex(rr->dp, true);
1737 : 797 : sh_require(sh, PL_SHADER_SIG_NONE, 0, 0);
1738 : :
1739 : : // Initialize the color to black
1740 : 797 : GLSL("vec4 color = vec4("$", vec2("$"), 1.0); \n"
1741 : : "// pass_read_image \n"
1742 : : "{ \n"
1743 : : "vec4 tmp; \n",
1744 : : SH_FLOAT(neutral_luma), SH_FLOAT(neutral_chroma));
1745 : :
1746 : : // For quality reasons, explicitly drop subpixel offsets from the ref rect
1747 : : // and re-add them as part of `pass->img.rect`, always rounding towards 0.
1748 : : // Additionally, drop anamorphic subpixel mismatches.
1749 : 797 : const pl_rect2df ref_rc = ref->img.rect;
1750 : : pl_rect2d ref_rounded;
1751 : 797 : ref_rounded.x0 = truncf(ref_rc.x0);
1752 : 797 : ref_rounded.y0 = truncf(ref_rc.y0);
1753 : 797 : ref_rounded.x1 = ref_rounded.x0 + roundf(pl_rect_w(ref_rc));
1754 : 797 : ref_rounded.y1 = ref_rounded.y0 + roundf(pl_rect_h(ref_rc));
1755 : :
1756 : 797 : PL_TRACE(rr, "Rounded reference rect: {%d %d %d %d}",
1757 : : ref_rounded.x0, ref_rounded.y0,
1758 : : ref_rounded.x1, ref_rounded.y1);
1759 : :
1760 : 797 : float off_x = ref_rc.x0 - ref_rounded.x0,
1761 : 797 : off_y = ref_rc.y0 - ref_rounded.y0,
1762 : 797 : stretch_x = pl_rect_w(ref_rounded) / pl_rect_w(ref_rc),
1763 : 797 : stretch_y = pl_rect_h(ref_rounded) / pl_rect_h(ref_rc);
1764 : :
1765 [ + + ]: 1604 : for (int i = 0; i < image->num_planes; i++) {
1766 : : struct plane_state *st = &planes[i];
1767 : : const struct pl_plane *plane = &st->plane;
1768 [ + + ]: 807 : if (!st->type)
1769 : 5 : continue;
1770 : :
1771 : 802 : float scale_x = pl_rect_w(st->img.rect) / pl_rect_w(ref_rc),
1772 : 802 : scale_y = pl_rect_h(st->img.rect) / pl_rect_h(ref_rc),
1773 : 802 : base_x = st->img.rect.x0 - scale_x * off_x,
1774 : 802 : base_y = st->img.rect.y0 - scale_y * off_y;
1775 : :
1776 : 1604 : struct pl_sample_src src = {
1777 : 802 : .components = plane->components,
1778 : 802 : .address_mode = plane->address_mode,
1779 : 802 : .scale = pl_color_repr_normalize(&st->img.repr),
1780 : : .new_w = pl_rect_w(ref_rounded),
1781 : : .new_h = pl_rect_h(ref_rounded),
1782 : : .rect = {
1783 : : base_x,
1784 : : base_y,
1785 : 802 : base_x + stretch_x * pl_rect_w(st->img.rect),
1786 : 802 : base_y + stretch_y * pl_rect_h(st->img.rect),
1787 : : },
1788 : : };
1789 : :
1790 [ - + ]: 802 : if (plane->flipped) {
1791 : 0 : src.rect.y0 = st->plane_h - src.rect.y0;
1792 : 0 : src.rect.y1 = st->plane_h - src.rect.y1;
1793 : : }
1794 : :
1795 [ + - ]: 1604 : PL_TRACE(rr, "Aligning plane %d: {%f %f %f %f} -> {%f %f %f %f}%s",
1796 : : i, st->img.rect.x0, st->img.rect.y0,
1797 : : st->img.rect.x1, st->img.rect.y1,
1798 : : src.rect.x0, src.rect.y0,
1799 : : src.rect.x1, src.rect.y1,
1800 : : plane->flipped ? " (flipped) " : "");
1801 : :
1802 : 802 : st->img.unique = true;
1803 : 802 : pl_rect2d unscaled = { .x1 = src.new_w, .y1 = src.new_h };
1804 [ + + + - ]: 802 : if (st->img.sh && st->img.w == src.new_w && st->img.h == src.new_h &&
1805 [ + - + - : 22 : pl_rect2d_eq(src.rect, unscaled))
+ - - + ]
1806 : : {
1807 : : // Image rects are already equal, no indirect scaling needed
1808 : : } else {
1809 : 780 : src.tex = img_tex(pass, &st->img);
1810 : 780 : st->img.tex = NULL;
1811 : 780 : st->img.sh = pl_dispatch_begin_ex(rr->dp, true);
1812 : 780 : dispatch_sampler(pass, st->img.sh, &rr->samplers_src[i],
1813 : : SAMPLER_PLANE, NULL, &src);
1814 : 780 : st->img.err_enum |= PL_RENDER_ERR_SAMPLING;
1815 : 780 : st->img.rect.x0 = st->img.rect.y0 = 0.0f;
1816 : 780 : st->img.w = st->img.rect.x1 = src.new_w;
1817 : 780 : st->img.h = st->img.rect.y1 = src.new_h;
1818 : 780 : src.scale = 1.0;
1819 : : }
1820 : :
1821 : 802 : pass_hook(pass, &st->img, plane_scaled_hook_stages[st->type]);
1822 : 802 : ident_t sub = sh_subpass(sh, img_sh(pass, &st->img));
1823 [ - + ]: 802 : if (!sub) {
1824 [ # # ]: 0 : if (!img_tex(pass, &st->img)) {
1825 : 0 : pl_dispatch_abort(rr->dp, &sh);
1826 : 0 : return false;
1827 : : }
1828 : :
1829 : 0 : sub = sh_subpass(sh, img_sh(pass, &st->img));
1830 [ # # ]: 0 : pl_assert(sub);
1831 : : }
1832 : :
1833 : 802 : GLSL("tmp = vec4("$") * "$"(); \n", SH_FLOAT(src.scale), sub);
1834 [ + + ]: 1609 : for (int c = 0; c < src.components; c++) {
1835 [ - + ]: 807 : if (plane->component_mapping[c] < 0)
1836 : 0 : continue;
1837 : 807 : GLSL("color[%d] = tmp[%d];\n", plane->component_mapping[c],
1838 : : st->fmt->sample_order[c]);
1839 : : }
1840 : :
1841 : : // we don't need it anymore
1842 : 802 : pl_dispatch_abort(rr->dp, &st->img.sh);
1843 : : }
1844 : :
1845 : 797 : GLSL("}\n");
1846 : :
1847 : 1594 : pass->img = (struct img) {
1848 : : .sh = sh,
1849 : : .w = pl_rect_w(ref_rounded),
1850 : : .h = pl_rect_h(ref_rounded),
1851 : 797 : .repr = ref->img.repr,
1852 : : .color = image->color,
1853 : 797 : .comps = ref->img.repr.alpha == PL_ALPHA_NONE ? 3 : 4,
1854 : : .rect = {
1855 : : off_x,
1856 : : off_y,
1857 : 797 : off_x + pl_rect_w(ref_rc),
1858 [ - + ]: 797 : off_y + pl_rect_h(ref_rc),
1859 : : },
1860 : : };
1861 : :
1862 : : // Update the reference rect to our adjusted image coordinates
1863 : 797 : pass->ref_rect = pass->img.rect;
1864 : :
1865 : 797 : pass_hook(pass, &pass->img, PL_HOOK_NATIVE);
1866 : :
1867 : : // Apply LUT logic and colorspace conversion
1868 : : enum pl_lut_type lut_type = guess_frame_lut_type(image, false);
1869 : 797 : sh = img_sh(pass, &pass->img);
1870 : : bool needs_conversion = true;
1871 : :
1872 [ + + ]: 797 : if (lut_type == PL_LUT_NATIVE || lut_type == PL_LUT_CONVERSION) {
1873 : : // Fix bit depth normalization before applying LUT
1874 : 24 : float scale = pl_color_repr_normalize(&pass->img.repr);
1875 : 24 : GLSL("color *= vec4("$"); \n", SH_FLOAT(scale));
1876 : 24 : pl_shader_set_alpha(sh, &pass->img.repr, PL_ALPHA_INDEPENDENT);
1877 : 24 : pl_shader_custom_lut(sh, image->lut, &rr->lut_state[LUT_IMAGE]);
1878 : :
1879 [ + + ]: 24 : if (lut_type == PL_LUT_CONVERSION) {
1880 : 8 : pass->img.repr.sys = PL_COLOR_SYSTEM_RGB;
1881 : 8 : pass->img.repr.levels = PL_COLOR_LEVELS_FULL;
1882 : : needs_conversion = false;
1883 : : }
1884 : : }
1885 : :
1886 : : if (needs_conversion) {
1887 [ - + ]: 789 : if (pass->img.repr.sys == PL_COLOR_SYSTEM_XYZ)
1888 : 0 : pass->img.color.transfer = PL_COLOR_TRC_LINEAR;
1889 : 789 : pl_shader_decode_color(sh, &pass->img.repr, params->color_adjustment);
1890 : : }
1891 : :
1892 [ + + ]: 797 : if (lut_type == PL_LUT_NORMALIZED)
1893 : 8 : pl_shader_custom_lut(sh, image->lut, &rr->lut_state[LUT_IMAGE]);
1894 : :
1895 : : // A main PL_LUT_CONVERSION LUT overrides ICC profiles
1896 [ + + + + ]: 797 : bool main_lut_override = params->lut && params->lut_type == PL_LUT_CONVERSION;
1897 [ + + + - ]: 797 : if (image->icc && !main_lut_override) {
1898 : 8 : pl_shader_set_alpha(sh, &pass->img.repr, PL_ALPHA_INDEPENDENT);
1899 : 8 : pl_icc_decode(sh, image->icc, &rr->icc_state[ICC_IMAGE], &pass->img.color);
1900 : : }
1901 : :
1902 : : // Pre-multiply alpha channel before the rest of the pipeline, to avoid
1903 : : // bleeding colors from transparent regions into non-transparent regions
1904 : 797 : pl_shader_set_alpha(sh, &pass->img.repr, PL_ALPHA_PREMULTIPLIED);
1905 : :
1906 : 797 : pass_hook(pass, &pass->img, PL_HOOK_RGB);
1907 : : sh = NULL;
1908 : 797 : return true;
1909 : : }
1910 : :
1911 : 797 : static bool pass_scale_main(struct pass_state *pass)
1912 : : {
1913 : 797 : const struct pl_render_params *params = pass->params;
1914 : 797 : pl_renderer rr = pass->rr;
1915 : :
1916 : 797 : pl_fmt fbofmt = pass->fbofmt[pass->img.comps];
1917 [ + + ]: 797 : if (!fbofmt) {
1918 : 4 : PL_TRACE(rr, "Skipping main scaler (no FBOs)");
1919 : 4 : return true;
1920 : : }
1921 : :
1922 : 793 : const pl_rect2df new_rect = {
1923 : 793 : .x1 = abs(pl_rect_w(pass->dst_rect)),
1924 : 793 : .y1 = abs(pl_rect_h(pass->dst_rect)),
1925 : : };
1926 : :
1927 : 793 : struct img *img = &pass->img;
1928 : 793 : struct pl_sample_src src = {
1929 : : .components = img->comps,
1930 : : .new_w = pl_rect_w(new_rect),
1931 : : .new_h = pl_rect_h(new_rect),
1932 : : .rect = img->rect,
1933 : : };
1934 : :
1935 : : const struct pl_frame *image = &pass->image;
1936 : : bool need_fbo = false;
1937 : :
1938 : : // Force FBO indirection if this shader is non-resizable
1939 : : int out_w, out_h;
1940 [ + + + + ]: 793 : if (img->sh && pl_shader_output_size(img->sh, &out_w, &out_h))
1941 [ + - - + ]: 22 : need_fbo |= out_w != src.new_w || out_h != src.new_h;
1942 : :
1943 : 793 : struct sampler_info info = sample_src_info(pass, &src, SAMPLER_MAIN);
1944 [ + + - + ]: 793 : bool use_sigmoid = info.dir == SAMPLER_UP && params->sigmoid_params;
1945 : 793 : bool use_linear = info.dir == SAMPLER_DOWN;
1946 : :
1947 : : // Opportunistically update peak here if it would save performance
1948 [ + + ]: 793 : if (info.dir == SAMPLER_UP)
1949 : 108 : hdr_update_peak(pass);
1950 : :
1951 : : // We need to enable the full rendering pipeline if there are any user
1952 : : // shaders / hooks that might depend on it.
1953 : : uint64_t scaling_hooks = PL_HOOK_PRE_KERNEL | PL_HOOK_POST_KERNEL;
1954 : : uint64_t linear_hooks = PL_HOOK_LINEAR | PL_HOOK_SIGMOID;
1955 : :
1956 [ + + ]: 808 : for (int i = 0; i < params->num_hooks; i++) {
1957 [ - + ]: 15 : if (params->hooks[i]->stages & (scaling_hooks | linear_hooks)) {
1958 : : need_fbo = true;
1959 [ # # ]: 0 : if (params->hooks[i]->stages & linear_hooks)
1960 : : use_linear = true;
1961 [ # # ]: 0 : if (params->hooks[i]->stages & PL_HOOK_SIGMOID)
1962 : : use_sigmoid = true;
1963 : : }
1964 : : }
1965 : :
1966 [ + + + - ]: 793 : if (info.dir == SAMPLER_NOOP && !need_fbo) {
1967 [ - + ]: 577 : pl_assert(src.new_w == img->w && src.new_h == img->h);
1968 : 577 : PL_TRACE(rr, "Skipping main scaler (would be no-op)");
1969 : 577 : goto done;
1970 : : }
1971 : :
1972 [ + + + - ]: 216 : if (info.type == SAMPLER_DIRECT && !need_fbo) {
1973 : 16 : img->w = src.new_w;
1974 : 16 : img->h = src.new_h;
1975 : 16 : img->rect = new_rect;
1976 : 16 : PL_TRACE(rr, "Skipping main scaler (free sampling)");
1977 : 16 : goto done;
1978 : : }
1979 : :
1980 : : // Hard-disable both sigmoidization and linearization when required
1981 [ + - - + ]: 200 : if (params->disable_linear_scaling || fbofmt->component_depth[0] < 16)
1982 : : use_sigmoid = use_linear = false;
1983 : :
1984 : : // Avoid sigmoidization for HDR content because it clips to [0,1], and
1985 : : // linearization because it causes very nasty ringing artefacts.
1986 [ - + ]: 200 : if (pl_color_space_is_hdr(&img->color))
1987 : : use_sigmoid = use_linear = false;
1988 : :
1989 [ - + - - ]: 200 : if (!(use_linear || use_sigmoid) && img->color.transfer == PL_COLOR_TRC_LINEAR) {
1990 : 0 : img->color.transfer = image->color.transfer;
1991 [ # # ]: 0 : if (image->color.transfer == PL_COLOR_TRC_LINEAR)
1992 : 0 : img->color.transfer = PL_COLOR_TRC_GAMMA22; // arbitrary fallback
1993 : 0 : pl_shader_delinearize(img_sh(pass, img), &img->color);
1994 : : }
1995 : :
1996 [ + - ]: 200 : if (use_linear || use_sigmoid) {
1997 : 200 : pl_shader_linearize(img_sh(pass, img), &img->color);
1998 : 200 : img->color.transfer = PL_COLOR_TRC_LINEAR;
1999 : 200 : pass_hook(pass, img, PL_HOOK_LINEAR);
2000 : : }
2001 : :
2002 [ + + ]: 200 : if (use_sigmoid) {
2003 : 96 : pl_shader_sigmoidize(img_sh(pass, img), params->sigmoid_params);
2004 : 96 : pass_hook(pass, img, PL_HOOK_SIGMOID);
2005 : : }
2006 : :
2007 : 200 : pass_hook(pass, img, PL_HOOK_PRE_KERNEL);
2008 : :
2009 : 200 : src.tex = img_tex(pass, img);
2010 [ + - ]: 200 : if (!src.tex)
2011 : : return false;
2012 : 200 : pass->need_peak_fbo = false;
2013 : :
2014 : 200 : pl_shader sh = pl_dispatch_begin_ex(rr->dp, true);
2015 : 200 : dispatch_sampler(pass, sh, &rr->sampler_main, SAMPLER_MAIN, NULL, &src);
2016 : 200 : img->tex = NULL;
2017 : 200 : img->sh = sh;
2018 : 200 : img->w = src.new_w;
2019 : 200 : img->h = src.new_h;
2020 : 200 : img->rect = new_rect;
2021 : :
2022 : 200 : pass_hook(pass, img, PL_HOOK_POST_KERNEL);
2023 : :
2024 [ + + ]: 200 : if (use_sigmoid)
2025 : 96 : pl_shader_unsigmoidize(img_sh(pass, img), params->sigmoid_params);
2026 : :
2027 : 104 : done:
2028 [ + + ]: 793 : if (info.dir != SAMPLER_UP)
2029 : 685 : hdr_update_peak(pass);
2030 : 793 : pass_hook(pass, img, PL_HOOK_SCALED);
2031 : 793 : return true;
2032 : : }
2033 : :
2034 : 789 : static pl_tex get_feature_map(struct pass_state *pass)
2035 : : {
2036 : 789 : const struct pl_render_params *params = pass->params;
2037 : 789 : pl_renderer rr = pass->rr;
2038 : 789 : const struct pl_color_map_params *cparams = params->color_map_params;
2039 [ + + ]: 789 : cparams = PL_DEF(cparams, &pl_color_map_default_params);
2040 [ - + - - ]: 789 : if (!cparams->contrast_recovery || cparams->contrast_smoothness <= 1)
2041 : : return NULL;
2042 [ # # ]: 0 : if (!pass->fbofmt[4])
2043 : : return NULL;
2044 [ # # ]: 0 : if (!pl_color_space_is_hdr(&pass->img.color))
2045 : : return NULL;
2046 [ # # ]: 0 : if (rr->errors & (PL_RENDER_ERR_SAMPLING | PL_RENDER_ERR_CONTRAST_RECOVERY))
2047 : : return NULL;
2048 [ # # ]: 0 : if (pass->img.color.hdr.max_luma <= pass->target.color.hdr.max_luma + 1e-6)
2049 : : return NULL; // no adaptation needed
2050 [ # # # # ]: 0 : if (params->lut && params->lut_type == PL_LUT_CONVERSION)
2051 : : return NULL; // LUT handles tone mapping
2052 : :
2053 : 0 : struct img *img = &pass->img;
2054 [ # # ]: 0 : if (!img_tex(pass, img))
2055 : : return NULL;
2056 : :
2057 : 0 : const float ratio = cparams->contrast_smoothness;
2058 : 0 : const int cr_w = ceilf(abs(pl_rect_w(pass->dst_rect)) / ratio);
2059 : 0 : const int cr_h = ceilf(abs(pl_rect_h(pass->dst_rect)) / ratio);
2060 : 0 : pl_tex inter_tex = get_fbo(pass, img->w, img->h, NULL, 1, PL_DEBUG_TAG);
2061 : 0 : pl_tex out_tex = get_fbo(pass, cr_w, cr_h, NULL, 1, PL_DEBUG_TAG);
2062 [ # # ]: 0 : if (!inter_tex || !out_tex)
2063 : 0 : goto error;
2064 : :
2065 : 0 : pl_shader sh = pl_dispatch_begin(rr->dp);
2066 : 0 : pl_shader_sample_direct(sh, pl_sample_src( .tex = img->tex ));
2067 : 0 : pl_shader_extract_features(sh, img->color);
2068 : 0 : bool ok = pl_dispatch_finish(rr->dp, pl_dispatch_params(
2069 : : .shader = &sh,
2070 : : .target = inter_tex,
2071 : : ));
2072 [ # # ]: 0 : if (!ok)
2073 : 0 : goto error;
2074 : :
2075 : 0 : const struct pl_sample_src src = {
2076 : : .tex = inter_tex,
2077 : : .rect = img->rect,
2078 : : .address_mode = PL_TEX_ADDRESS_MIRROR,
2079 : : .components = 1,
2080 : : .new_w = cr_w,
2081 : : .new_h = cr_h,
2082 : : };
2083 : :
2084 : 0 : sh = pl_dispatch_begin(rr->dp);
2085 : 0 : dispatch_sampler(pass, sh, &rr->sampler_contrast, SAMPLER_CONTRAST, out_tex, &src);
2086 : 0 : ok = pl_dispatch_finish(rr->dp, pl_dispatch_params(
2087 : : .shader = &sh,
2088 : : .target = out_tex,
2089 : : ));
2090 [ # # ]: 0 : if (!ok)
2091 : 0 : goto error;
2092 : :
2093 : : return out_tex;
2094 : :
2095 : 0 : error:
2096 : 0 : PL_ERR(rr, "Failed extracting luma for contrast recovery, disabling");
2097 : 0 : rr->errors |= PL_RENDER_ERR_CONTRAST_RECOVERY;
2098 : 0 : return NULL;
2099 : : }
2100 : :
2101 : : // Transforms image into the output color space (tone-mapping, ICC 3DLUT, etc)
2102 : 797 : static void pass_convert_colors(struct pass_state *pass)
2103 : : {
2104 : 797 : const struct pl_render_params *params = pass->params;
2105 : : const struct pl_frame *image = &pass->image;
2106 : : const struct pl_frame *target = &pass->target;
2107 : 797 : pl_renderer rr = pass->rr;
2108 : :
2109 : 797 : struct img *img = &pass->img;
2110 : 797 : pl_shader sh = img_sh(pass, img);
2111 : :
2112 : : bool prelinearized = false;
2113 : : bool need_conversion = true;
2114 [ - + ]: 797 : assert(image->color.primaries == img->color.primaries);
2115 [ + + ]: 797 : if (img->color.transfer == PL_COLOR_TRC_LINEAR) {
2116 [ - + ]: 208 : if (img->repr.alpha == PL_ALPHA_PREMULTIPLIED) {
2117 : : // Very annoying edge case: since prelinerization happens with
2118 : : // premultiplied alpha, but color mapping happens with independent
2119 : : // alpha, we need to go back to non-linear representation *before*
2120 : : // alpha mode conversion, to avoid distortion
2121 : 0 : img->color.transfer = image->color.transfer;
2122 : 0 : pl_shader_delinearize(sh, &img->color);
2123 : : } else {
2124 : : prelinearized = true;
2125 : : }
2126 [ + - ]: 589 : } else if (img->color.transfer != image->color.transfer) {
2127 [ # # ]: 0 : if (image->color.transfer == PL_COLOR_TRC_LINEAR) {
2128 : : // Another annoying edge case: if the input is linear light, but we
2129 : : // decide to un-linearize it for scaling purposes, we need to
2130 : : // re-linearize before passing it into `pl_shader_color_map`
2131 : 0 : pl_shader_linearize(sh, &img->color);
2132 : 0 : img->color.transfer = PL_COLOR_TRC_LINEAR;
2133 : : }
2134 : : }
2135 : :
2136 : : // Do all processing in independent alpha, to avoid nonlinear distortions
2137 : 797 : pl_shader_set_alpha(sh, &img->repr, PL_ALPHA_INDEPENDENT);
2138 : :
2139 : : // Apply color blindness simulation if requested
2140 [ + + ]: 797 : if (params->cone_params)
2141 : 4 : pl_shader_cone_distort(sh, img->color, params->cone_params);
2142 : :
2143 [ + + ]: 797 : if (params->lut) {
2144 : 32 : struct pl_color_space lut_in = params->lut->color_in;
2145 : 32 : struct pl_color_space lut_out = params->lut->color_out;
2146 [ - + + + ]: 32 : switch (params->lut_type) {
2147 : 16 : case PL_LUT_UNKNOWN:
2148 : : case PL_LUT_NATIVE:
2149 : 16 : pl_color_space_merge(&lut_in, &image->color);
2150 : 16 : pl_color_space_merge(&lut_out, &image->color);
2151 : 16 : break;
2152 : 8 : case PL_LUT_CONVERSION:
2153 : 8 : pl_color_space_merge(&lut_in, &image->color);
2154 : : need_conversion = false; // conversion LUT the highest priority
2155 : 8 : break;
2156 : 8 : case PL_LUT_NORMALIZED:
2157 [ + - ]: 8 : if (!prelinearized) {
2158 : : // PL_LUT_NORMALIZED wants linear input data
2159 : 8 : pl_shader_linearize(sh, &img->color);
2160 : 8 : img->color.transfer = PL_COLOR_TRC_LINEAR;
2161 : : prelinearized = true;
2162 : : }
2163 : 8 : pl_color_space_merge(&lut_in, &img->color);
2164 : 8 : pl_color_space_merge(&lut_out, &img->color);
2165 : 8 : break;
2166 : : }
2167 : :
2168 : 32 : pl_shader_color_map_ex(sh, params->color_map_params, pl_color_map_args(
2169 : : .src = image->color,
2170 : : .dst = lut_in,
2171 : : .prelinearized = prelinearized,
2172 : : ));
2173 : :
2174 [ + + ]: 32 : if (params->lut_type == PL_LUT_NORMALIZED) {
2175 : 8 : GLSLF("color.rgb *= vec3(1.0/"$"); \n",
2176 : : SH_FLOAT(pl_color_transfer_nominal_peak(lut_in.transfer)));
2177 : : }
2178 : :
2179 : 32 : pl_shader_custom_lut(sh, params->lut, &rr->lut_state[LUT_PARAMS]);
2180 : :
2181 [ + + ]: 32 : if (params->lut_type == PL_LUT_NORMALIZED) {
2182 : 8 : GLSLF("color.rgb *= vec3("$"); \n",
2183 : : SH_FLOAT(pl_color_transfer_nominal_peak(lut_out.transfer)));
2184 : : }
2185 : :
2186 [ + + ]: 32 : if (params->lut_type != PL_LUT_CONVERSION) {
2187 : 24 : pl_shader_color_map_ex(sh, params->color_map_params, pl_color_map_args(
2188 : : .src = lut_out,
2189 : : .dst = img->color,
2190 : : ));
2191 : : }
2192 : : }
2193 : :
2194 [ + + ]: 32 : if (need_conversion) {
2195 : 789 : struct pl_color_space target_csp = target->color;
2196 [ + + ]: 789 : if (target->icc)
2197 : : target_csp.transfer = PL_COLOR_TRC_LINEAR;
2198 : :
2199 [ + + - + ]: 789 : if (pass->need_peak_fbo && !img_tex(pass, img))
2200 : 0 : return;
2201 : :
2202 : : // generate HDR feature map if required
2203 : 789 : pl_tex feature_map = get_feature_map(pass);
2204 : 789 : sh = img_sh(pass, img); // `get_feature_map` dispatches previous shader
2205 : :
2206 : : // current -> target
2207 : 789 : pl_shader_color_map_ex(sh, params->color_map_params, pl_color_map_args(
2208 : : .src = image->color,
2209 : : .dst = target_csp,
2210 : : .prelinearized = prelinearized,
2211 : : .state = &rr->tone_map_state,
2212 : : .feature_map = feature_map,
2213 : : ));
2214 : :
2215 [ + + ]: 789 : if (target->icc)
2216 : 8 : pl_icc_encode(sh, target->icc, &rr->icc_state[ICC_TARGET]);
2217 : : }
2218 : :
2219 : : enum pl_lut_type lut_type = guess_frame_lut_type(target, true);
2220 [ + + ]: 797 : if (lut_type == PL_LUT_NORMALIZED || lut_type == PL_LUT_CONVERSION)
2221 : 16 : pl_shader_custom_lut(sh, target->lut, &rr->lut_state[LUT_TARGET]);
2222 : :
2223 : 797 : img->color = target->color;
2224 : : }
2225 : :
2226 : : // Returns true if error diffusion was successfully performed
2227 : 142 : static bool pass_error_diffusion(struct pass_state *pass, pl_shader *sh,
2228 : : int new_depth, int comps, int out_w, int out_h)
2229 : : {
2230 : 142 : const struct pl_render_params *params = pass->params;
2231 : 142 : pl_renderer rr = pass->rr;
2232 [ - + - - ]: 142 : if (!params->error_diffusion || (rr->errors & PL_RENDER_ERR_ERROR_DIFFUSION))
2233 : : return false;
2234 : :
2235 : 0 : size_t shmem_req = pl_error_diffusion_shmem_req(params->error_diffusion, out_h);
2236 [ # # ]: 0 : if (shmem_req > rr->gpu->glsl.max_shmem_size) {
2237 : 0 : PL_TRACE(rr, "Disabling error diffusion due to shmem requirements (%zu) "
2238 : : "exceeding capabilities (%zu)", shmem_req, rr->gpu->glsl.max_shmem_size);
2239 : 0 : return false;
2240 : : }
2241 : :
2242 : 0 : pl_fmt fmt = pass->fbofmt[comps];
2243 [ # # # # ]: 0 : if (!fmt || !(fmt->caps & PL_FMT_CAP_STORABLE)) {
2244 : 0 : PL_ERR(rr, "Error diffusion requires storable FBOs but GPU does not "
2245 : : "provide them... disabling!");
2246 : 0 : goto error;
2247 : : }
2248 : :
2249 : 0 : struct pl_error_diffusion_params edpars = {
2250 : : .new_depth = new_depth,
2251 : 0 : .kernel = params->error_diffusion,
2252 : : };
2253 : :
2254 : : // Create temporary framebuffers
2255 : 0 : edpars.input_tex = get_fbo(pass, out_w, out_h, fmt, comps, PL_DEBUG_TAG);
2256 : 0 : edpars.output_tex = get_fbo(pass, out_w, out_h, fmt, comps, PL_DEBUG_TAG);
2257 [ # # # # ]: 0 : if (!edpars.input_tex || !edpars.output_tex)
2258 : 0 : goto error;
2259 : :
2260 : 0 : pl_shader dsh = pl_dispatch_begin(rr->dp);
2261 [ # # ]: 0 : if (!pl_shader_error_diffusion(dsh, &edpars)) {
2262 : 0 : pl_dispatch_abort(rr->dp, &dsh);
2263 : 0 : goto error;
2264 : : }
2265 : :
2266 : : // Everything was okay, run the shaders
2267 : 0 : bool ok = pl_dispatch_finish(rr->dp, pl_dispatch_params(
2268 : : .shader = sh,
2269 : : .target = edpars.input_tex,
2270 : : ));
2271 : :
2272 [ # # ]: 0 : if (ok) {
2273 : 0 : ok = pl_dispatch_compute(rr->dp, pl_dispatch_compute_params(
2274 : : .shader = &dsh,
2275 : : .dispatch_size = {1, 1, 1},
2276 : : ));
2277 : : }
2278 : :
2279 : 0 : *sh = pl_dispatch_begin(rr->dp);
2280 [ # # ]: 0 : pl_shader_sample_direct(*sh, pl_sample_src(
2281 : : .tex = ok ? edpars.output_tex : edpars.input_tex,
2282 : : ));
2283 : 0 : return ok;
2284 : :
2285 : 0 : error:
2286 : 0 : rr->errors |= PL_RENDER_ERR_ERROR_DIFFUSION;
2287 : 0 : return false;
2288 : : }
2289 : :
2290 : : #define CLEAR_COL(params) \
2291 : : (float[4]) { \
2292 : : (params)->background_color[0], \
2293 : : (params)->background_color[1], \
2294 : : (params)->background_color[2], \
2295 : : 1.0 - (params)->background_transparency, \
2296 : : }
2297 : :
2298 : 120 : static void clear_target(pl_renderer rr, const struct pl_frame *target,
2299 : : const struct pl_render_params *params)
2300 : : {
2301 : 120 : enum pl_clear_mode border = params->border;
2302 [ + - ]: 120 : if (params->skip_target_clearing)
2303 : : border = PL_CLEAR_SKIP;
2304 : :
2305 [ + - - - : 120 : switch (border) {
- ]
2306 : 120 : case PL_CLEAR_COLOR:
2307 : 120 : pl_frame_clear_rgba(rr->gpu, target, CLEAR_COL(params));
2308 : 120 : break;
2309 : 0 : case PL_CLEAR_TILES:
2310 : 0 : pl_frame_clear_tiles(rr->gpu, target, params->tile_colors, params->tile_size);
2311 : 0 : break;
2312 : : case PL_CLEAR_SKIP: break;
2313 : 0 : case PL_CLEAR_MODE_COUNT: pl_unreachable();
2314 : : }
2315 : 120 : }
2316 : :
2317 : 1077 : static bool pass_output_target(struct pass_state *pass)
2318 : : {
2319 : 1077 : const struct pl_render_params *params = pass->params;
2320 : : const struct pl_frame *image = &pass->image;
2321 : 1077 : const struct pl_frame *target = &pass->target;
2322 : 1077 : pl_renderer rr = pass->rr;
2323 : :
2324 : 1077 : struct img *img = &pass->img;
2325 : 1077 : pl_shader sh = img_sh(pass, img);
2326 : :
2327 [ + + ]: 1077 : if (params->corner_rounding > 0.0f) {
2328 : 4 : const float out_w2 = fabsf(pl_rect_w(target->crop)) / 2.0f;
2329 : 4 : const float out_h2 = fabsf(pl_rect_h(target->crop)) / 2.0f;
2330 : 4 : const float radius = fminf(params->corner_rounding, 1.0f) *
2331 : 4 : fminf(out_w2, out_h2);
2332 : 4 : const struct pl_rect2df relpos = {
2333 : 4 : .x0 = -out_w2, .y0 = -out_h2,
2334 : : .x1 = out_w2, .y1 = out_h2,
2335 : : };
2336 : 4 : GLSL("float radius = "$"; \n"
2337 : : "vec2 size2 = vec2("$", "$"); \n"
2338 : : "vec2 relpos = "$"; \n"
2339 : : "vec2 rd = abs(relpos) - size2 + vec2(radius); \n"
2340 : : "float rdist = length(max(rd, 0.0)) - radius; \n"
2341 : : "float border = smoothstep(2.0f, 0.0f, rdist); \n",
2342 : : SH_FLOAT_DYN(radius),
2343 : : SH_FLOAT_DYN(out_w2), SH_FLOAT_DYN(out_h2),
2344 : : sh_attr_vec2(sh, "relpos", &relpos));
2345 : :
2346 [ + - - - : 4 : switch (img->repr.alpha) {
- ]
2347 : : case PL_ALPHA_UNKNOWN:
2348 : : case PL_ALPHA_NONE:
2349 : 4 : GLSL("color.a = border; \n");
2350 : 4 : img->repr.alpha = PL_ALPHA_INDEPENDENT;
2351 : 4 : img->comps = 4;
2352 : 4 : break;
2353 : : case PL_ALPHA_INDEPENDENT:
2354 : 0 : GLSL("color.a *= border; \n");
2355 : 0 : break;
2356 : : case PL_ALPHA_PREMULTIPLIED:
2357 : 0 : GLSL("color *= border; \n");
2358 : 0 : break;
2359 : : case PL_ALPHA_MODE_COUNT:
2360 : 0 : pl_unreachable();
2361 : : }
2362 : : }
2363 : :
2364 : 1077 : const struct pl_plane *ref = &target->planes[pass->dst_ref];
2365 : 1077 : pl_rect2d dst_rect = pass->dst_rect;
2366 [ + + ]: 1077 : if (params->distort_params) {
2367 : 24 : struct pl_distort_params dpars = *params->distort_params;
2368 [ + + ]: 24 : if (dpars.alpha_mode) {
2369 : 4 : pl_shader_set_alpha(sh, &img->repr, dpars.alpha_mode);
2370 : 4 : img->repr.alpha = dpars.alpha_mode;
2371 : 4 : img->comps = 4;
2372 : : }
2373 : 24 : pl_tex tex = img_tex(pass, img);
2374 [ - + ]: 24 : if (!tex)
2375 : 0 : return false;
2376 : : // Expand canvas to fit result of distortion
2377 : 24 : const float ar = pl_rect2df_aspect(&target->crop);
2378 : 24 : const float sx = fminf(ar, 1.0f);
2379 : 24 : const float sy = fminf(1.0f / ar, 1.0f);
2380 : 24 : pl_rect2df bb = pl_transform2x2_bounds(&dpars.transform, &(pl_rect2df) {
2381 : 24 : .x0 = -sx, .x1 = sx,
2382 : 24 : .y0 = -sy, .y1 = sy,
2383 : : });
2384 : :
2385 : : // Clamp to output size and adjust as needed when constraining output
2386 : 24 : pl_rect2df tmp = target->crop;
2387 : 24 : pl_rect2df_stretch(&tmp, pl_rect_w(bb) / (2*sx), pl_rect_h(bb) / (2*sy));
2388 : 24 : const float tmp_w = pl_rect_w(tmp), tmp_h = pl_rect_h(tmp);
2389 : 24 : int canvas_w = ref->texture->params.w,
2390 : 24 : canvas_h = ref->texture->params.h;
2391 [ - + ]: 24 : if (pass->rotation % PL_ROTATION_180 == PL_ROTATION_90)
2392 : : PL_SWAP(canvas_w, canvas_h);
2393 [ + - + - ]: 24 : tmp.x0 = PL_CLAMP(tmp.x0, 0.0f, canvas_w);
2394 [ + - + - ]: 24 : tmp.x1 = PL_CLAMP(tmp.x1, 0.0f, canvas_w);
2395 [ + - + - ]: 24 : tmp.y0 = PL_CLAMP(tmp.y0, 0.0f, canvas_h);
2396 [ + - + - ]: 24 : tmp.y1 = PL_CLAMP(tmp.y1, 0.0f, canvas_h);
2397 [ + + ]: 24 : if (dpars.constrain) {
2398 : 4 : const float rx = pl_rect_w(tmp) / tmp_w;
2399 : 4 : const float ry = pl_rect_h(tmp) / tmp_h;
2400 : 4 : pl_rect2df_stretch(&tmp, fminf(ry / rx, 1.0f), fminf(rx / ry, 1.0f));
2401 : : }
2402 : 24 : dst_rect.x0 = roundf(tmp.x0);
2403 : 24 : dst_rect.x1 = roundf(tmp.x1);
2404 : 24 : dst_rect.y0 = roundf(tmp.y0);
2405 : 24 : dst_rect.y1 = roundf(tmp.y1);
2406 : 24 : dpars.unscaled = true;
2407 : 24 : img->w = abs(pl_rect_w(dst_rect));
2408 : 24 : img->h = abs(pl_rect_h(dst_rect));
2409 : 24 : img->tex = NULL;
2410 : 24 : img->sh = sh = pl_dispatch_begin(rr->dp);
2411 : 24 : pl_shader_distort(sh, tex, img->w, img->h, &dpars);
2412 : : }
2413 : :
2414 : 1077 : pass_hook(pass, img, PL_HOOK_PRE_OUTPUT);
2415 : :
2416 : 1077 : enum pl_clear_mode background = params->background;
2417 [ + + ]: 1077 : if (params->blend_against_tiles)
2418 : : background = PL_CLEAR_TILES;
2419 [ - + ]: 1073 : else if (params->skip_target_clearing)
2420 : : background = PL_CLEAR_SKIP;
2421 : :
2422 [ + - - + ]: 1077 : bool has_alpha = target->repr.alpha != PL_ALPHA_NONE || params->blend_params;
2423 : 1077 : bool need_blend = background != PL_CLEAR_SKIP || !has_alpha;
2424 [ + + + - ]: 1077 : if (img->comps == 4 && need_blend) {
2425 : 10 : pl_shader_set_alpha(sh, &img->repr, PL_ALPHA_PREMULTIPLIED);
2426 [ + + - - ]: 10 : switch (background) {
2427 : : case PL_CLEAR_COLOR:
2428 : 6 : GLSL("color += (1.0 - color.a) * vec4("$", "$", "$", "$"); \n",
2429 : : SH_FLOAT(params->background_color[0]),
2430 : : SH_FLOAT(params->background_color[1]),
2431 : : SH_FLOAT(params->background_color[2]),
2432 : : SH_FLOAT(1.0 - params->background_transparency));
2433 [ + - ]: 6 : if (!params->background_transparency) {
2434 : 6 : img->repr.alpha = PL_ALPHA_NONE;
2435 : 6 : img->comps = 3;
2436 : : }
2437 : : break;
2438 : 4 : case PL_CLEAR_TILES:;
2439 : : static const float zero[2][3] = {0};
2440 : 4 : const float (*color)[3] = params->tile_colors;
2441 [ - + ]: 4 : if (memcmp(color, zero, sizeof(zero)) == 0)
2442 : : color = pl_render_default_params.tile_colors;
2443 [ + - ]: 4 : GLSL("vec2 outcoord = gl_FragCoord.xy * "$"; \n"
2444 : : "bvec2 tile = lessThan(fract(outcoord), vec2(0.5)); \n"
2445 : : "vec3 tile_color = tile.x == tile.y ? vec3("$", "$", "$") \n"
2446 : : " : vec3("$", "$", "$"); \n"
2447 : : "color.rgb += (1.0 - color.a) * tile_color; \n"
2448 : : "color.a = 1.0; \n",
2449 : : SH_FLOAT(1.0 / PL_DEF(params->tile_size, pl_render_default_params.tile_size)),
2450 : : SH_FLOAT(color[0][0]), SH_FLOAT(color[0][1]), SH_FLOAT(color[0][2]),
2451 : : SH_FLOAT(color[1][0]), SH_FLOAT(color[1][1]), SH_FLOAT(color[1][2]));
2452 : 4 : img->repr.alpha = PL_ALPHA_NONE;
2453 : 4 : img->comps = 3;
2454 : 4 : break;
2455 : : case PL_CLEAR_SKIP: break;
2456 : 0 : case PL_CLEAR_MODE_COUNT: pl_unreachable();
2457 : : }
2458 [ - + - - ]: 1067 : } else if (img->comps == 4 && has_alpha) {
2459 : 0 : pl_shader_set_alpha(sh, &img->repr, target->repr.alpha);
2460 : : }
2461 : :
2462 : : // Apply the color scale separately, after encoding is done, to make sure
2463 : : // that the intermediate FBO (if any) has the correct precision.
2464 : 1077 : struct pl_color_repr repr = target->repr;
2465 : 1077 : float scale = pl_color_repr_normalize(&repr);
2466 : : enum pl_lut_type lut_type = guess_frame_lut_type(target, true);
2467 [ + + ]: 24 : if (lut_type != PL_LUT_CONVERSION)
2468 : 1069 : pl_shader_encode_color(sh, &repr);
2469 [ + + ]: 1069 : if (lut_type == PL_LUT_NATIVE) {
2470 : 16 : pl_shader_set_alpha(sh, &img->repr, PL_ALPHA_INDEPENDENT);
2471 : 16 : pl_shader_custom_lut(sh, target->lut, &rr->lut_state[LUT_TARGET]);
2472 : 16 : pl_shader_set_alpha(sh, &img->repr, target->repr.alpha);
2473 : : }
2474 : :
2475 : : // Rotation handling
2476 [ + + ]: 1077 : if (pass->rotation % PL_ROTATION_180 == PL_ROTATION_90) {
2477 : : PL_SWAP(dst_rect.x0, dst_rect.y0);
2478 : : PL_SWAP(dst_rect.x1, dst_rect.y1);
2479 : 616 : PL_SWAP(img->w, img->h);
2480 : 616 : sh->transpose = true;
2481 : : }
2482 : :
2483 : 1077 : pass_hook(pass, img, PL_HOOK_OUTPUT);
2484 : 1077 : sh = NULL;
2485 : :
2486 : : bool flipped_x = dst_rect.x1 < dst_rect.x0,
2487 : : flipped_y = dst_rect.y1 < dst_rect.y0;
2488 : :
2489 [ + + ]: 1077 : if (pl_frame_is_cropped(target))
2490 : 108 : clear_target(rr, target, params);
2491 : :
2492 [ + + ]: 2164 : for (int p = 0; p < target->num_planes; p++) {
2493 : : const struct pl_plane *plane = &target->planes[p];
2494 : 1087 : float rx = (float) plane->texture->params.w / ref->texture->params.w,
2495 : 1087 : ry = (float) plane->texture->params.h / ref->texture->params.h;
2496 : :
2497 : : // Only accept integer scaling ratios. This accounts for the fact
2498 : : // that fractionally subsampled planes get rounded up to the
2499 : : // nearest integer size, which we want to over-render.
2500 [ + + ]: 1087 : float rrx = rx >= 1 ? roundf(rx) : 1.0 / roundf(1.0 / rx),
2501 [ + + ]: 1087 : rry = ry >= 1 ? roundf(ry) : 1.0 / roundf(1.0 / ry);
2502 : 1087 : float sx = plane->shift_x, sy = plane->shift_y;
2503 : :
2504 : 1087 : pl_rect2df plane_rectf = {
2505 : 1087 : .x0 = (dst_rect.x0 - sx) * rrx,
2506 : 1087 : .y0 = (dst_rect.y0 - sy) * rry,
2507 : 1087 : .x1 = (dst_rect.x1 - sx) * rrx,
2508 : 1087 : .y1 = (dst_rect.y1 - sy) * rry,
2509 : : };
2510 : :
2511 : : // Normalize to make the math easier
2512 : 1087 : pl_rect2df_normalize(&plane_rectf);
2513 : :
2514 : : // Round the output rect
2515 : 1087 : int rx0 = floorf(plane_rectf.x0), ry0 = floorf(plane_rectf.y0),
2516 : 1087 : rx1 = ceilf(plane_rectf.x1), ry1 = ceilf(plane_rectf.y1);
2517 : :
2518 : 1087 : PL_TRACE(rr, "Subsampled target %d: {%f %f %f %f} -> {%d %d %d %d}",
2519 : : p, plane_rectf.x0, plane_rectf.y0,
2520 : : plane_rectf.x1, plane_rectf.y1,
2521 : : rx0, ry0, rx1, ry1);
2522 : :
2523 [ + + ]: 1087 : if (target->num_planes > 1) {
2524 : :
2525 : : // Planar output, so we need to sample from an intermediate FBO
2526 : 30 : struct pl_sample_src src = {
2527 : 15 : .tex = img_tex(pass, img),
2528 : 15 : .new_w = rx1 - rx0,
2529 : 15 : .new_h = ry1 - ry0,
2530 : : .rect = {
2531 : 15 : .x0 = (rx0 - plane_rectf.x0) / rrx,
2532 : 15 : .x1 = (rx1 - plane_rectf.x0) / rrx,
2533 : 15 : .y0 = (ry0 - plane_rectf.y0) / rry,
2534 : 15 : .y1 = (ry1 - plane_rectf.y0) / rry,
2535 : : },
2536 : : };
2537 : :
2538 [ - + ]: 15 : if (!src.tex) {
2539 : 0 : PL_ERR(rr, "Output requires multiple planes, but FBOs are "
2540 : : "unavailable. This combination is unsupported.");
2541 : 0 : return false;
2542 : : }
2543 : :
2544 : 15 : PL_TRACE(rr, "Sampling %dx%d img aligned from {%f %f %f %f}",
2545 : : pass->img.w, pass->img.h,
2546 : : src.rect.x0, src.rect.y0,
2547 : : src.rect.x1, src.rect.y1);
2548 : :
2549 [ + + ]: 30 : for (int c = 0; c < plane->components; c++) {
2550 [ - + ]: 15 : if (plane->component_mapping[c] < 0)
2551 : 0 : continue;
2552 : 15 : src.component_mask |= 1 << plane->component_mapping[c];
2553 : : }
2554 : :
2555 [ - + ]: 15 : if (params->blend_params) /* preserve alpha if blending */
2556 : 0 : src.component_mask |= 1 << PL_CHANNEL_A;
2557 : :
2558 : 15 : sh = pl_dispatch_begin(rr->dp);
2559 : 15 : dispatch_sampler(pass, sh, &rr->samplers_dst[p], SAMPLER_PLANE,
2560 : 15 : plane->texture, &src);
2561 : :
2562 : : } else {
2563 : :
2564 : : // Single plane, so we can directly re-use the img shader unless
2565 : : // it's incompatible with the FBO capabilities
2566 : 1072 : bool is_comp = pl_shader_is_compute(img_sh(pass, img));
2567 [ + + - + ]: 1072 : if (is_comp && !plane->texture->params.storable) {
2568 [ # # ]: 0 : if (!img_tex(pass, img)) {
2569 : 0 : PL_ERR(rr, "Rendering requires compute shaders, but output "
2570 : : "is not storable, and FBOs are unavailable. This "
2571 : : "combination is unsupported.");
2572 : 0 : return false;
2573 : : }
2574 : : }
2575 : :
2576 : 1072 : sh = img_sh(pass, img);
2577 : 1072 : img->sh = NULL;
2578 : :
2579 : : }
2580 : :
2581 : : // Ignore dithering for > 16-bit outputs by default, since it makes
2582 : : // little sense to do so (and probably just adds errors)
2583 : 1087 : int depth = target->repr.bits.color_depth, applied_dither = 0;
2584 [ - + + + : 1087 : if (depth && (depth < 16 || params->force_dither)) {
+ + ]
2585 [ - + ]: 142 : if (pass_error_diffusion(pass, &sh, depth, plane->components,
2586 : : rx1 - rx0, ry1 - ry0))
2587 : : {
2588 : : applied_dither = depth;
2589 [ - + ]: 142 : } else if (params->dither_params) {
2590 : 142 : struct pl_dither_params dparams = *params->dither_params;
2591 [ + - ]: 142 : if (!params->disable_dither_gamma_correction)
2592 : 142 : dparams.transfer = target->color.transfer;
2593 : 142 : pl_shader_dither(sh, depth, &rr->dither_state, &dparams);
2594 : : applied_dither = depth;
2595 : : }
2596 : : }
2597 : :
2598 [ + + ]: 1087 : if (applied_dither != rr->prev_dither) {
2599 [ + + ]: 16 : if (applied_dither) {
2600 : 12 : PL_INFO(rr, "Dithering to %d bit depth", applied_dither);
2601 : : } else {
2602 : 4 : PL_INFO(rr, "Dithering disabled");
2603 : : }
2604 : 16 : rr->prev_dither = applied_dither;
2605 : : }
2606 : :
2607 [ + - + - ]: 3261 : GLSL("color.%s *= vec%d(1.0 / "$"); \n",
2608 : : params->blend_params ? "rgb" : "rgba",
2609 : : params->blend_params ? 3 : 4,
2610 : : SH_FLOAT(scale));
2611 : :
2612 : 1087 : swizzle_color(sh, plane->components, plane->component_mapping,
2613 : 1087 : params->blend_params);
2614 : :
2615 : : pl_rect2d plane_rect = {
2616 [ + + ]: 1087 : .x0 = flipped_x ? rx1 : rx0,
2617 [ + + ]: 1087 : .x1 = flipped_x ? rx0 : rx1,
2618 [ + + ]: 1087 : .y0 = flipped_y ? ry1 : ry0,
2619 [ + + ]: 1087 : .y1 = flipped_y ? ry0 : ry1,
2620 : : };
2621 : :
2622 : 1087 : pl_transform2x2 tscale = {
2623 : : .mat = {{{ rrx, 0.0 }, { 0.0, rry }}},
2624 : 1087 : .c = { -sx, -sy },
2625 : : };
2626 : :
2627 [ - + ]: 1087 : if (plane->flipped) {
2628 : 0 : int plane_h = rry * ref->texture->params.h;
2629 : 0 : plane_rect.y0 = plane_h - plane_rect.y0;
2630 : 0 : plane_rect.y1 = plane_h - plane_rect.y1;
2631 : 0 : tscale.mat.m[1][1] = -tscale.mat.m[1][1];
2632 : 0 : tscale.c[1] += plane->texture->params.h;
2633 : : }
2634 : :
2635 : 1087 : bool ok = pl_dispatch_finish(rr->dp, pl_dispatch_params(
2636 : : .shader = &sh,
2637 : : .target = plane->texture,
2638 : : .blend_params = params->blend_params,
2639 : : .rect = plane_rect,
2640 : : ));
2641 : :
2642 [ + - ]: 1087 : if (!ok)
2643 : : return false;
2644 : :
2645 [ + + ]: 1087 : if (pass->info.stage != PL_RENDER_STAGE_BLEND) {
2646 : 487 : draw_overlays(pass, plane->texture, plane->components,
2647 : 487 : plane->component_mapping, image->overlays,
2648 : 487 : image->num_overlays, target->color, target->repr,
2649 : : &tscale);
2650 : : }
2651 : :
2652 : 1087 : draw_overlays(pass, plane->texture, plane->components,
2653 : 1087 : plane->component_mapping, target->overlays,
2654 : 1087 : target->num_overlays, target->color, target->repr,
2655 : : &tscale);
2656 : : }
2657 : :
2658 : 1077 : *img = (struct img) {0};
2659 : 1077 : return true;
2660 : : }
2661 : :
2662 : : #define require(expr) pl_require(rr, expr)
2663 : : #define validate_plane(plane, param) \
2664 : : do { \
2665 : : require((plane).texture); \
2666 : : require((plane).texture->params.param); \
2667 : : require((plane).components > 0 && (plane).components <= 4); \
2668 : : for (int c = 0; c < (plane).components; c++) { \
2669 : : require((plane).component_mapping[c] >= PL_CHANNEL_NONE && \
2670 : : (plane).component_mapping[c] <= PL_CHANNEL_A); \
2671 : : } \
2672 : : } while (0)
2673 : :
2674 : : #define validate_overlay(overlay) \
2675 : : do { \
2676 : : require((overlay).tex); \
2677 : : require((overlay).tex->params.sampleable); \
2678 : : require((overlay).num_parts >= 0); \
2679 : : for (int n = 0; n < (overlay).num_parts; n++) { \
2680 : : const struct pl_overlay_part *p = &(overlay).parts[n]; \
2681 : : require(pl_rect_w(p->dst) && pl_rect_h(p->dst)); \
2682 : : } \
2683 : : } while (0)
2684 : :
2685 : : #define validate_deinterlace_ref(image, ref) \
2686 : : do { \
2687 : : require((image)->num_planes == (ref)->num_planes); \
2688 : : const struct pl_tex_params *imgp, *refp; \
2689 : : for (int p = 0; p < (image)->num_planes; p++) { \
2690 : : validate_plane((ref)->planes[p], sampleable); \
2691 : : imgp = &(image)->planes[p].texture->params; \
2692 : : refp = &(ref)->planes[p].texture->params; \
2693 : : require(imgp->w == refp->w); \
2694 : : require(imgp->h == refp->h); \
2695 : : require(imgp->format->num_components == refp->format->num_components);\
2696 : : } \
2697 : : } while (0)
2698 : :
2699 : : // Perform some basic validity checks on incoming structs to help catch invalid
2700 : : // API usage. This is not an exhaustive check. In particular, enums are not
2701 : : // bounds checked. This is because most functions accepting enums already
2702 : : // abort() in the default case, and because it's not the intent of this check
2703 : : // to catch all instances of memory corruption - just common logic bugs.
2704 : 1409 : static bool validate_structs(pl_renderer rr,
2705 : : const struct pl_frame *image,
2706 : : const struct pl_frame *target)
2707 : : {
2708 : : // Rendering to/from a frame with no planes is technically allowed, but so
2709 : : // pointless that it's more likely to be a user error worth catching.
2710 [ - + ]: 1409 : require(target->num_planes > 0 && target->num_planes <= PL_MAX_PLANES);
2711 [ + + ]: 2828 : for (int i = 0; i < target->num_planes; i++)
2712 [ - + - + : 5646 : validate_plane(target->planes[i], renderable);
- + - + +
+ ]
2713 [ - + ]: 1409 : require(!pl_rect_w(target->crop) == !pl_rect_h(target->crop));
2714 [ - + ]: 1409 : require(target->num_overlays >= 0);
2715 [ + + ]: 1417 : for (int i = 0; i < target->num_overlays; i++)
2716 [ - + - + : 16 : validate_overlay(target->overlays[i]);
- + + - -
+ + + ]
2717 : :
2718 [ + + ]: 1409 : if (!image)
2719 : : return true;
2720 : :
2721 [ - + ]: 797 : require(image->num_planes > 0 && image->num_planes <= PL_MAX_PLANES);
2722 [ + + ]: 1604 : for (int i = 0; i < image->num_planes; i++)
2723 [ - + - + : 1614 : validate_plane(image->planes[i], sampleable);
- + - + +
+ ]
2724 [ - + ]: 797 : require(!pl_rect_w(image->crop) == !pl_rect_h(image->crop));
2725 [ - + ]: 797 : require(image->num_overlays >= 0);
2726 [ + + ]: 805 : for (int i = 0; i < image->num_overlays; i++)
2727 [ - + - + : 24 : validate_overlay(image->overlays[i]);
- + + - -
+ + + ]
2728 : :
2729 [ + + ]: 797 : if (image->field != PL_FIELD_NONE) {
2730 [ - + ]: 160 : require(image->first_field != PL_FIELD_NONE);
2731 [ + + ]: 160 : if (image->prev)
2732 [ - + - + : 456 : validate_deinterlace_ref(image, image->prev);
- + - + -
+ + + - +
- + - + +
+ ]
2733 [ + + ]: 160 : if (image->next)
2734 [ - + - + : 456 : validate_deinterlace_ref(image, image->next);
- + - + -
+ + + - +
- + - + +
+ ]
2735 : : }
2736 : :
2737 : : return true;
2738 : :
2739 : : error:
2740 : : return false;
2741 : : }
2742 : :
2743 : : // returns index
2744 : 6750 : static int frame_ref(const struct pl_frame *frame)
2745 : : {
2746 [ + - ]: 6750 : pl_assert(frame->num_planes);
2747 [ + - ]: 6750 : for (int i = 0; i < frame->num_planes; i++) {
2748 [ - - - + ]: 6750 : switch (detect_plane_type(&frame->planes[i], &frame->repr)) {
2749 : : case PLANE_RGB:
2750 : : case PLANE_LUMA:
2751 : : case PLANE_XYZ:
2752 : : return i;
2753 : 0 : case PLANE_CHROMA:
2754 : : case PLANE_ALPHA:
2755 : 0 : continue;
2756 : : case PLANE_INVALID:
2757 : 0 : pl_unreachable();
2758 : : }
2759 : : }
2760 : :
2761 : : return 0;
2762 : : }
2763 : :
2764 : 1421 : static void fix_refs_and_rects(struct pass_state *pass)
2765 : : {
2766 : 1421 : struct pl_frame *target = &pass->target;
2767 : 1421 : pl_rect2df *dst = &target->crop;
2768 : 1421 : pass->dst_ref = frame_ref(target);
2769 : 1421 : pl_tex dst_ref = target->planes[pass->dst_ref].texture;
2770 : 1421 : int dst_w = dst_ref->params.w, dst_h = dst_ref->params.h;
2771 : :
2772 [ + - + + : 1421 : if ((!dst->x0 && !dst->x1) || (!dst->y0 && !dst->y1)) {
+ - - + ]
2773 : 1309 : dst->x1 = dst_w;
2774 : 1309 : dst->y1 = dst_h;
2775 : : }
2776 : :
2777 [ + + ]: 1421 : if (pass->src_ref < 0) {
2778 : : // Simplified version of the below code which only rounds the target
2779 : : // rect but doesn't retroactively apply the crop to the image
2780 : 8 : pass->rotation = pl_rotation_normalize(-target->rotation);
2781 : 8 : pl_rect2df_rotate(dst, -pass->rotation);
2782 [ - + ]: 8 : if (pass->rotation % PL_ROTATION_180 == PL_ROTATION_90)
2783 : : PL_SWAP(dst_w, dst_h);
2784 : :
2785 : 8 : *dst = (pl_rect2df) {
2786 [ + - + - ]: 8 : .x0 = roundf(PL_CLAMP(dst->x0, 0.0, dst_w)),
2787 [ + - + - ]: 8 : .y0 = roundf(PL_CLAMP(dst->y0, 0.0, dst_h)),
2788 [ + - + - ]: 8 : .x1 = roundf(PL_CLAMP(dst->x1, 0.0, dst_w)),
2789 [ + - + - ]: 8 : .y1 = roundf(PL_CLAMP(dst->y1, 0.0, dst_h)),
2790 : : };
2791 : :
2792 : 8 : pass->dst_rect = (pl_rect2d) {
2793 : : dst->x0, dst->y0, dst->x1, dst->y1,
2794 : : };
2795 : :
2796 : : return;
2797 : : }
2798 : :
2799 : 1413 : struct pl_frame *image = &pass->image;
2800 : 1413 : pl_rect2df *src = &image->crop;
2801 : 1413 : pass->src_ref = frame_ref(image);
2802 : 1413 : pl_tex src_ref = image->planes[pass->src_ref].texture;
2803 : :
2804 [ + - + + : 1413 : if ((!src->x0 && !src->x1) || (!src->y0 && !src->y1)) {
+ - - + ]
2805 : 1301 : src->x1 = src_ref->params.w;
2806 : 1301 : src->y1 = src_ref->params.h;
2807 : : };
2808 : :
2809 : : // Compute end-to-end rotation
2810 : 1413 : pass->rotation = pl_rotation_normalize(image->rotation - target->rotation);
2811 : 1413 : pl_rect2df_rotate(dst, -pass->rotation); // normalize by counter-rotating
2812 [ + + ]: 1413 : if (pass->rotation % PL_ROTATION_180 == PL_ROTATION_90)
2813 : : PL_SWAP(dst_w, dst_h);
2814 : :
2815 : : // Keep track of whether the end-to-end rendering is flipped
2816 : 1413 : bool flipped_x = (src->x0 > src->x1) != (dst->x0 > dst->x1),
2817 : 1413 : flipped_y = (src->y0 > src->y1) != (dst->y0 > dst->y1);
2818 : :
2819 : : // Normalize both rects to make the math easier
2820 : 1413 : pl_rect2df_normalize(src);
2821 : 1413 : pl_rect2df_normalize(dst);
2822 : :
2823 : : // Round the output rect and clip it to the framebuffer dimensions
2824 [ + - + - ]: 1413 : float rx0 = roundf(PL_CLAMP(dst->x0, 0.0, dst_w)),
2825 [ + - + - ]: 1413 : ry0 = roundf(PL_CLAMP(dst->y0, 0.0, dst_h)),
2826 [ + - + - ]: 1413 : rx1 = roundf(PL_CLAMP(dst->x1, 0.0, dst_w)),
2827 [ + - + - ]: 1413 : ry1 = roundf(PL_CLAMP(dst->y1, 0.0, dst_h));
2828 : :
2829 : : // Adjust the src rect corresponding to the rounded crop
2830 : 1413 : float scale_x = pl_rect_w(*src) / pl_rect_w(*dst),
2831 : 1413 : scale_y = pl_rect_h(*src) / pl_rect_h(*dst),
2832 : : base_x = src->x0,
2833 : : base_y = src->y0;
2834 : :
2835 : 1413 : src->x0 = base_x + (rx0 - dst->x0) * scale_x;
2836 : 1413 : src->x1 = base_x + (rx1 - dst->x0) * scale_x;
2837 : 1413 : src->y0 = base_y + (ry0 - dst->y0) * scale_y;
2838 : 1413 : src->y1 = base_y + (ry1 - dst->y0) * scale_y;
2839 : :
2840 : : // Update dst_rect to the rounded values and re-apply flip if needed. We
2841 : : // always do this in the `dst` rather than the `src`` because this allows
2842 : : // e.g. polar sampling compute shaders to work.
2843 : 1413 : *dst = (pl_rect2df) {
2844 [ + + ]: 1413 : .x0 = flipped_x ? rx1 : rx0,
2845 [ + + ]: 1413 : .y0 = flipped_y ? ry1 : ry0,
2846 [ + + ]: 1413 : .x1 = flipped_x ? rx0 : rx1,
2847 [ + + ]: 1413 : .y1 = flipped_y ? ry0 : ry1,
2848 : : };
2849 : :
2850 : : // Copies of the above, for convenience
2851 : 1413 : pass->ref_rect = *src;
2852 : 1413 : pass->dst_rect = (pl_rect2d) {
2853 : : dst->x0, dst->y0, dst->x1, dst->y1,
2854 : : };
2855 : : }
2856 : :
2857 : 2834 : static void fix_frame(struct pl_frame *frame)
2858 : : {
2859 : 2834 : pl_tex tex = frame->planes[frame_ref(frame)].texture;
2860 : :
2861 [ - + ]: 2834 : if (frame->repr.sys == PL_COLOR_SYSTEM_XYZ) {
2862 : : // XYZ is implicity converted to linear DCI-P3 in pl_color_repr_decode
2863 : 0 : frame->color.primaries = PL_COLOR_PRIM_DCI_P3;
2864 : 0 : frame->color.transfer = PL_COLOR_TRC_ST428;
2865 : : }
2866 : :
2867 : : // If the primaries are not known, guess them based on the resolution
2868 [ + - - + ]: 2834 : if (tex && !frame->color.primaries)
2869 : 0 : frame->color.primaries = pl_color_primaries_guess(tex->params.w, tex->params.h);
2870 : :
2871 : : // For UNORM formats, we can infer the sampled bit depth from the texture
2872 : : // itself. This is ignored for other format types, because the logic
2873 : : // doesn't really work out for them anyways, and it's best not to do
2874 : : // anything too crazy unless the user provides explicit details.
2875 : : struct pl_bit_encoding *bits = &frame->repr.bits;
2876 [ + - + - : 2834 : if (!bits->sample_depth && tex && tex->params.format->type == PL_FMT_UNORM) {
+ + ]
2877 : : // Just assume the first component's depth is canonical. This works in
2878 : : // practice, since for cases like rgb565 we want to use the lower depth
2879 : : // anyway. Plus, every format has at least one component.
2880 : 10 : bits->sample_depth = tex->params.format->component_depth[0];
2881 : :
2882 : : // If we don't know the color depth, assume it spans the full range of
2883 : : // the texture. Otherwise, clamp it to the texture depth.
2884 [ + - ]: 10 : bits->color_depth = PL_DEF(bits->color_depth, bits->sample_depth);
2885 : 10 : bits->color_depth = PL_MIN(bits->color_depth, bits->sample_depth);
2886 : :
2887 : : // If the texture depth is higher than the known color depth, assume
2888 : : // the colors were left-shifted.
2889 : 10 : bits->bit_shift += bits->sample_depth - bits->color_depth;
2890 : : }
2891 : 2834 : }
2892 : :
2893 : : static bool acquire_frame(struct pass_state *pass, struct pl_frame *frame,
2894 : : bool *acquired)
2895 : : {
2896 [ - + - - : 2206 : if (!frame || !frame->acquire || *acquired)
- + - - -
- - - ]
2897 : : return true;
2898 : :
2899 : 0 : *acquired = true;
2900 : 0 : return frame->acquire(pass->rr->gpu, frame);
2901 : : }
2902 : :
2903 : : static void release_frame(struct pass_state *pass, struct pl_frame *frame,
2904 : : bool *acquired)
2905 : : {
2906 [ - - - + : 4227 : if (frame && frame->release && *acquired)
- - - + -
- - + -
- ]
2907 : 0 : frame->release(pass->rr->gpu, frame);
2908 : 5636 : *acquired = false;
2909 : : }
2910 : :
2911 : 1409 : static void pass_uninit(struct pass_state *pass)
2912 : : {
2913 : 1409 : pl_renderer rr = pass->rr;
2914 : 1409 : pl_dispatch_abort(rr->dp, &pass->img.sh);
2915 [ + - ]: 1409 : release_frame(pass, &pass->next, &pass->acquired.next);
2916 : 1409 : release_frame(pass, &pass->prev, &pass->acquired.prev);
2917 : 1409 : release_frame(pass, &pass->image, &pass->acquired.image);
2918 : 1409 : release_frame(pass, &pass->target, &pass->acquired.target);
2919 : 1409 : pl_free_ptr(&pass->tmp);
2920 : 1409 : }
2921 : :
2922 : 2842 : static void icc_fallback(struct pass_state *pass, struct pl_frame *frame,
2923 : : struct icc_state *fallback)
2924 : : {
2925 [ + + + - : 2842 : if (!frame || frame->icc || !frame->profile.data)
+ + ]
2926 : : return;
2927 : :
2928 : : // Don't re-attempt opening already failed profiles
2929 [ - + - - ]: 16 : if (fallback->error && fallback->error == frame->profile.signature)
2930 : : return;
2931 : :
2932 : : #ifdef PL_HAVE_LCMS
2933 : 16 : pl_renderer rr = pass->rr;
2934 [ + - ]: 16 : if (pl_icc_update(rr->log, &fallback->icc, &frame->profile, NULL)) {
2935 : 16 : frame->icc = fallback->icc;
2936 : : } else {
2937 : 0 : PL_WARN(rr, "Failed opening ICC profile... ignoring");
2938 : 0 : fallback->error = frame->profile.signature;
2939 : : }
2940 : : #endif
2941 : : }
2942 : :
2943 : 1421 : static void pass_fix_frames(struct pass_state *pass)
2944 : : {
2945 : 1421 : pl_renderer rr = pass->rr;
2946 [ + + ]: 1421 : struct pl_frame *image = pass->src_ref < 0 ? NULL : &pass->image;
2947 : 1421 : struct pl_frame *target = &pass->target;
2948 : :
2949 : 1421 : fix_refs_and_rects(pass);
2950 : :
2951 : : // Fallback for older ICC profile API
2952 : 1421 : icc_fallback(pass, image, &rr->icc_fallback[ICC_IMAGE]);
2953 : 1421 : icc_fallback(pass, target, &rr->icc_fallback[ICC_TARGET]);
2954 : :
2955 : : // Force colorspace metadata to ICC profile values, if present
2956 [ + + + + ]: 1421 : if (image && image->icc) {
2957 : 8 : image->color.primaries = image->icc->containing_primaries;
2958 : 8 : image->color.hdr = image->icc->csp.hdr;
2959 [ + - ]: 8 : if (image->icc->csp.transfer)
2960 : 8 : image->color.transfer = image->icc->csp.transfer;
2961 : : }
2962 : :
2963 [ + + ]: 1421 : if (target->icc) {
2964 : 8 : target->color.primaries = target->icc->containing_primaries;
2965 : 8 : target->color.hdr = target->icc->csp.hdr;
2966 [ + - ]: 8 : if (target->icc->csp.transfer)
2967 : 8 : target->color.transfer = target->icc->csp.transfer;
2968 : : }
2969 : :
2970 : : // Infer the target color space info based on the image's
2971 [ + + ]: 1421 : if (image) {
2972 : 1413 : fix_frame(image);
2973 : 1413 : pl_color_space_infer_map(&image->color, &target->color);
2974 : 1413 : fix_frame(target); // do this only after infer_map
2975 : : } else {
2976 : 8 : fix_frame(target);
2977 : 8 : pl_color_space_infer(&target->color);
2978 : : }
2979 : :
2980 : : // Detect the presence of an alpha channel in the frames and explicitly
2981 : : // default the alpha mode in this case, so we can use it to detect whether
2982 : : // or not to strip the alpha channel during rendering.
2983 : : //
2984 : : // Note the different defaults for the image and target, because files
2985 : : // are usually independent but windowing systems usually expect
2986 : : // premultiplied. (We also premultiply for internal rendering, so this
2987 : : // way of doing it avoids a possible division-by-zero path!)
2988 [ + + + + ]: 1421 : if (image && !image->repr.alpha) {
2989 : 1409 : image->repr.alpha = PL_ALPHA_NONE;
2990 [ + + ]: 2828 : for (int i = 0; i < image->num_planes; i++) {
2991 : : const struct pl_plane *plane = &image->planes[i];
2992 [ + + ]: 2838 : for (int c = 0; c < plane->components; c++) {
2993 [ - + ]: 1419 : if (plane->component_mapping[c] == PL_CHANNEL_A)
2994 : 0 : image->repr.alpha = PL_ALPHA_INDEPENDENT;
2995 : : }
2996 : : }
2997 : : }
2998 : :
2999 [ + + ]: 1421 : if (!target->repr.alpha) {
3000 : 1417 : target->repr.alpha = PL_ALPHA_NONE;
3001 [ + + ]: 2844 : for (int i = 0; i < target->num_planes; i++) {
3002 : : const struct pl_plane *plane = &target->planes[i];
3003 [ + + ]: 5678 : for (int c = 0; c < plane->components; c++) {
3004 [ - + ]: 4251 : if (plane->component_mapping[c] == PL_CHANNEL_A)
3005 : 0 : target->repr.alpha = PL_ALPHA_PREMULTIPLIED;
3006 : : }
3007 : : }
3008 : : }
3009 : 1421 : }
3010 : :
3011 : 4 : void pl_frames_infer(pl_renderer rr, struct pl_frame *image,
3012 : : struct pl_frame *target)
3013 : : {
3014 : 4 : struct pass_state pass = {
3015 : : .rr = rr,
3016 : : .image = *image,
3017 : : .target = *target,
3018 : : };
3019 : :
3020 : 4 : pass_fix_frames(&pass);
3021 : 4 : *image = pass.image;
3022 : 4 : *target = pass.target;
3023 : 4 : }
3024 : :
3025 : 1409 : static bool pass_init(struct pass_state *pass, bool acquire_image)
3026 : : {
3027 [ + + ]: 1409 : struct pl_frame *image = pass->src_ref < 0 ? NULL : &pass->image;
3028 : 1409 : struct pl_frame *target = &pass->target;
3029 : :
3030 [ # # ]: 0 : if (!acquire_frame(pass, target, &pass->acquired.target))
3031 : 0 : goto error;
3032 [ + + ]: 1409 : if (acquire_image && image) {
3033 [ # # ]: 0 : if (!acquire_frame(pass, image, &pass->acquired.image))
3034 : 0 : goto error;
3035 : :
3036 : 797 : const struct pl_render_params *params = pass->params;
3037 : 797 : const struct pl_deinterlace_params *deint = params->deinterlace_params;
3038 [ + + + - ]: 797 : bool needs_refs = image->field != PL_FIELD_NONE && deint &&
3039 [ # # ]: 0 : pl_deinterlace_needs_refs(deint->algo);
3040 : :
3041 [ + + - + ]: 797 : if (image->prev && needs_refs) {
3042 : : // Move into local copy so we can acquire/release it
3043 : 0 : pass->prev = *image->prev;
3044 [ # # ]: 0 : image->prev = &pass->prev;
3045 [ # # ]: 0 : if (!acquire_frame(pass, &pass->prev, &pass->acquired.prev))
3046 : 0 : goto error;
3047 : : }
3048 [ + + - + ]: 797 : if (image->next && needs_refs) {
3049 : 0 : pass->next = *image->next;
3050 [ # # ]: 0 : image->next = &pass->next;
3051 [ # # ]: 0 : if (!acquire_frame(pass, &pass->next, &pass->acquired.next))
3052 : 0 : goto error;
3053 : : }
3054 : : }
3055 : :
3056 [ + + - + ]: 2021 : if (!validate_structs(pass->rr, acquire_image ? image : NULL, target))
3057 : 0 : goto error;
3058 : :
3059 : 1409 : find_fbo_format(pass);
3060 : 1409 : pass_fix_frames(pass);
3061 : :
3062 : 1409 : pass->tmp = pl_tmp(NULL);
3063 : 1409 : return true;
3064 : :
3065 : 0 : error:
3066 : 0 : pass_uninit(pass);
3067 : 0 : return false;
3068 : : }
3069 : :
3070 : 1401 : static void pass_begin_frame(struct pass_state *pass)
3071 : : {
3072 : 1401 : pl_renderer rr = pass->rr;
3073 : 1401 : const struct pl_render_params *params = pass->params;
3074 : :
3075 : 1401 : pl_dispatch_callback(rr->dp, pass, info_callback);
3076 : 1401 : pl_dispatch_reset_frame(rr->dp);
3077 : :
3078 [ + + ]: 1416 : for (int i = 0; i < params->num_hooks; i++) {
3079 [ + + ]: 15 : if (params->hooks[i]->reset)
3080 : 10 : params->hooks[i]->reset(params->hooks[i]->priv);
3081 : : }
3082 : :
3083 : 1401 : size_t size = rr->fbos.num * sizeof(bool);
3084 : 1401 : pass->fbos_used = pl_realloc(pass->tmp, pass->fbos_used, size);
3085 : : memset(pass->fbos_used, 0, size);
3086 : 1401 : }
3087 : :
3088 : 12 : static bool draw_empty_overlays(pl_renderer rr,
3089 : : const struct pl_frame *ptarget,
3090 : : const struct pl_render_params *params)
3091 : : {
3092 : 12 : clear_target(rr, ptarget, params);
3093 [ + + ]: 12 : if (!ptarget->num_overlays)
3094 : : return true;
3095 : :
3096 : 4 : struct pass_state pass = {
3097 : : .rr = rr,
3098 : : .params = params,
3099 : : .src_ref = -1,
3100 : : .target = *ptarget,
3101 : : .info.stage = PL_RENDER_STAGE_BLEND,
3102 : : .info.count = 0,
3103 : : };
3104 : :
3105 [ + - ]: 4 : if (!pass_init(&pass, false))
3106 : : return false;
3107 : :
3108 : 4 : pass_begin_frame(&pass);
3109 : : struct pl_frame *target = &pass.target;
3110 : 4 : pl_tex ref = target->planes[pass.dst_ref].texture;
3111 [ + + ]: 8 : for (int p = 0; p < target->num_planes; p++) {
3112 : : const struct pl_plane *plane = &target->planes[p];
3113 : : // Math replicated from `pass_output_target`
3114 : 4 : float rx = (float) plane->texture->params.w / ref->params.w,
3115 : 4 : ry = (float) plane->texture->params.h / ref->params.h;
3116 [ + - ]: 4 : float rrx = rx >= 1 ? roundf(rx) : 1.0 / roundf(1.0 / rx),
3117 [ + - ]: 4 : rry = ry >= 1 ? roundf(ry) : 1.0 / roundf(1.0 / ry);
3118 : 4 : float sx = plane->shift_x, sy = plane->shift_y;
3119 : :
3120 : 4 : pl_transform2x2 tscale = {
3121 : : .mat = {{{ rrx, 0.0 }, { 0.0, rry }}},
3122 : 4 : .c = { -sx, -sy },
3123 : : };
3124 : :
3125 [ - + ]: 4 : if (plane->flipped) {
3126 : 0 : tscale.mat.m[1][1] = -tscale.mat.m[1][1];
3127 : 0 : tscale.c[1] += plane->texture->params.h;
3128 : : }
3129 : :
3130 : 4 : draw_overlays(&pass, plane->texture, plane->components,
3131 : 4 : plane->component_mapping, target->overlays,
3132 : : target->num_overlays, target->color, target->repr,
3133 : : &tscale);
3134 : : }
3135 : :
3136 : 4 : pass_uninit(&pass);
3137 : 4 : return true;
3138 : : }
3139 : :
3140 : 489 : bool pl_render_image(pl_renderer rr, const struct pl_frame *pimage,
3141 : : const struct pl_frame *ptarget,
3142 : : const struct pl_render_params *params)
3143 : : {
3144 [ + + ]: 489 : params = PL_DEF(params, &pl_render_default_params);
3145 : 489 : pl_dispatch_mark_dynamic(rr->dp, params->dynamic_constants);
3146 [ + + ]: 489 : if (!pimage)
3147 : 12 : return draw_empty_overlays(rr, ptarget, params);
3148 : :
3149 : 477 : struct pass_state pass = {
3150 : : .rr = rr,
3151 : : .params = params,
3152 : : .image = *pimage,
3153 : : .target = *ptarget,
3154 : : .info.stage = PL_RENDER_STAGE_FRAME,
3155 : : };
3156 : :
3157 [ - + ]: 477 : if (!pass_init(&pass, true))
3158 : : return false;
3159 : :
3160 : : // No-op (empty crop)
3161 [ + - - + ]: 477 : if (!pl_rect_w(pass.dst_rect) || !pl_rect_h(pass.dst_rect)) {
3162 : 0 : pass_uninit(&pass);
3163 : 0 : return draw_empty_overlays(rr, ptarget, params);
3164 : : }
3165 : :
3166 : 477 : pass_begin_frame(&pass);
3167 [ - + ]: 477 : if (!pass_read_image(&pass))
3168 : 0 : goto error;
3169 [ - + ]: 477 : if (!pass_scale_main(&pass))
3170 : 0 : goto error;
3171 : 477 : pass_convert_colors(&pass);
3172 [ - + ]: 477 : if (!pass_output_target(&pass))
3173 : 0 : goto error;
3174 : :
3175 : 477 : pass_uninit(&pass);
3176 : 477 : return true;
3177 : :
3178 : 0 : error:
3179 : 0 : PL_ERR(rr, "Failed rendering image!");
3180 : 0 : pass_uninit(&pass);
3181 : 0 : return false;
3182 : : }
3183 : :
3184 : 0 : const struct pl_frame *pl_frame_mix_current(const struct pl_frame_mix *mix)
3185 : : {
3186 : : const struct pl_frame *cur = NULL;
3187 [ # # ]: 0 : for (int i = 0; i < mix->num_frames; i++) {
3188 [ # # ]: 0 : if (mix->timestamps[i] > 0.0f)
3189 : : break;
3190 : 0 : cur = mix->frames[i];
3191 : : }
3192 : :
3193 : 0 : return cur;
3194 : : }
3195 : :
3196 : 616 : const struct pl_frame *pl_frame_mix_nearest(const struct pl_frame_mix *mix)
3197 : : {
3198 [ + + ]: 616 : if (!mix->num_frames)
3199 : : return NULL;
3200 : :
3201 : 612 : const struct pl_frame *best = mix->frames[0];
3202 : 612 : float best_dist = fabsf(mix->timestamps[0]);
3203 [ + + ]: 1224 : for (int i = 1; i < mix->num_frames; i++) {
3204 : 1036 : float dist = fabsf(mix->timestamps[i]);
3205 [ + + ]: 1036 : if (dist < best_dist) {
3206 : 612 : best = mix->frames[i];
3207 : : best_dist = dist;
3208 : : continue;
3209 : : } else {
3210 : : break;
3211 : : }
3212 : : }
3213 : :
3214 : : return best;
3215 : : }
3216 : :
3217 : : struct params_info {
3218 : : uint64_t hash;
3219 : : bool trivial;
3220 : : };
3221 : :
3222 : 608 : static struct params_info render_params_info(const struct pl_render_params *params_orig)
3223 : : {
3224 : 608 : struct pl_render_params params = *params_orig;
3225 : : struct params_info info = {
3226 : : .trivial = true,
3227 : : .hash = 0,
3228 : : };
3229 : :
3230 : : #define HASH_PTR(ptr, def, ptr_trivial) \
3231 : : do { \
3232 : : if (ptr) { \
3233 : : pl_hash_merge(&info.hash, pl_mem_hash(ptr, sizeof(*ptr))); \
3234 : : info.trivial &= (ptr_trivial); \
3235 : : ptr = NULL; \
3236 : : } else if ((def) != NULL) { \
3237 : : pl_hash_merge(&info.hash, pl_mem_hash(def, sizeof(*ptr))); \
3238 : : } \
3239 : : } while (0)
3240 : :
3241 : : #define HASH_FILTER(scaler) \
3242 : : do { \
3243 : : if ((scaler == &pl_filter_bilinear || scaler == &pl_filter_nearest) && \
3244 : : params.skip_anti_aliasing) \
3245 : : { \
3246 : : /* treat as NULL */ \
3247 : : } else if (scaler) { \
3248 : : struct pl_filter_config filter = *scaler; \
3249 : : HASH_PTR(filter.kernel, NULL, false); \
3250 : : HASH_PTR(filter.window, NULL, false); \
3251 : : pl_hash_merge(&info.hash, pl_var_hash(filter)); \
3252 : : scaler = NULL; \
3253 : : } \
3254 : : } while (0)
3255 : :
3256 [ + - - + : 608 : HASH_FILTER(params.upscaler);
- - - + -
- - - ]
3257 [ + - - + : 608 : HASH_FILTER(params.downscaler);
- - - + -
- - - ]
3258 : :
3259 [ - + ]: 608 : HASH_PTR(params.deband_params, NULL, false);
3260 [ - + ]: 608 : HASH_PTR(params.sigmoid_params, NULL, false);
3261 [ - + ]: 608 : HASH_PTR(params.deinterlace_params, NULL, false);
3262 [ - + ]: 608 : HASH_PTR(params.cone_params, NULL, true);
3263 [ - + ]: 608 : HASH_PTR(params.icc_params, &pl_icc_default_params, true);
3264 [ - + ]: 608 : HASH_PTR(params.color_adjustment, &pl_color_adjustment_neutral, true);
3265 [ - + ]: 608 : HASH_PTR(params.color_map_params, &pl_color_map_default_params, true);
3266 [ - + ]: 608 : HASH_PTR(params.peak_detect_params, NULL, false);
3267 : :
3268 : : // Hash all hooks
3269 [ - + ]: 608 : for (int i = 0; i < params.num_hooks; i++) {
3270 : 0 : const struct pl_hook *hook = params.hooks[i];
3271 [ # # ]: 0 : if (hook->stages == PL_HOOK_OUTPUT)
3272 : 0 : continue; // ignore hooks only relevant to pass_output_target
3273 : : pl_hash_merge(&info.hash, pl_var_hash(*hook));
3274 : : info.trivial = false;
3275 : : }
3276 : 608 : params.hooks = NULL;
3277 : :
3278 : : // Hash the LUT by only looking at the signature
3279 [ - + ]: 608 : if (params.lut) {
3280 : 0 : pl_hash_merge(&info.hash, params.lut->signature);
3281 : : info.trivial = false;
3282 : 0 : params.lut = NULL;
3283 : : }
3284 : :
3285 : : #define CLEAR(field) field = (__typeof__(field)) {0}
3286 : :
3287 : : // Clear out fields only relevant to pl_render_image_mix
3288 : 608 : CLEAR(params.frame_mixer);
3289 : 608 : CLEAR(params.preserve_mixing_cache);
3290 : 608 : CLEAR(params.skip_caching_single_frame);
3291 : :
3292 : : // Clear out fields only relevant to pass_output_target
3293 : 608 : CLEAR(params.background);
3294 : 608 : CLEAR(params.border);
3295 : 608 : CLEAR(params.skip_target_clearing);
3296 : 608 : CLEAR(params.blend_against_tiles);
3297 : : memset(params.background_color, 0, sizeof(params.background_color));
3298 : 608 : CLEAR(params.background_transparency);
3299 : : memset(params.tile_colors, 0, sizeof(params.tile_colors));
3300 : 608 : CLEAR(params.tile_size);
3301 : 608 : CLEAR(params.blend_params);
3302 : 608 : CLEAR(params.distort_params);
3303 : 608 : CLEAR(params.dither_params);
3304 : 608 : CLEAR(params.error_diffusion);
3305 : 608 : CLEAR(params.force_dither);
3306 : 608 : CLEAR(params.corner_rounding);
3307 : :
3308 : : // Clear out other irrelevant fields
3309 : 608 : CLEAR(params.dynamic_constants);
3310 : 608 : CLEAR(params.info_callback);
3311 : 608 : CLEAR(params.info_priv);
3312 : :
3313 : : pl_hash_merge(&info.hash, pl_var_hash(params));
3314 : 608 : return info;
3315 : : }
3316 : :
3317 : : #define MAX_MIX_FRAMES 16
3318 : :
3319 : 616 : bool pl_render_image_mix(pl_renderer rr, const struct pl_frame_mix *images,
3320 : : const struct pl_frame *ptarget,
3321 : : const struct pl_render_params *params)
3322 : : {
3323 [ + + ]: 616 : if (!images->num_frames)
3324 : 8 : return pl_render_image(rr, NULL, ptarget, params);
3325 : :
3326 [ - + ]: 608 : params = PL_DEF(params, &pl_render_default_params);
3327 : 608 : struct params_info par_info = render_params_info(params);
3328 : 608 : pl_dispatch_mark_dynamic(rr->dp, params->dynamic_constants);
3329 : :
3330 [ - + ]: 608 : require(images->num_frames >= 1);
3331 [ - + ]: 608 : require(images->vsync_duration > 0.0);
3332 [ + + ]: 1744 : for (int i = 0; i < images->num_frames - 1; i++)
3333 [ - + ]: 1136 : require(images->timestamps[i] <= images->timestamps[i+1]);
3334 : :
3335 : 608 : const struct pl_frame *refimg = pl_frame_mix_nearest(images);
3336 : 608 : struct pass_state pass = {
3337 : : .rr = rr,
3338 : : .params = params,
3339 : : .image = *refimg,
3340 : : .target = *ptarget,
3341 : : .info.stage = PL_RENDER_STAGE_BLEND,
3342 : : };
3343 : :
3344 [ - + ]: 608 : if (rr->errors & PL_RENDER_ERR_FRAME_MIXING)
3345 : 0 : goto fallback;
3346 [ + - ]: 608 : if (!pass_init(&pass, false))
3347 : : return false;
3348 [ - + ]: 608 : if (!pass.fbofmt[4])
3349 : 0 : goto fallback;
3350 : :
3351 : : const struct pl_frame *target = &pass.target;
3352 : 608 : int out_w = abs(pl_rect_w(pass.dst_rect)),
3353 : 608 : out_h = abs(pl_rect_h(pass.dst_rect));
3354 [ - + ]: 608 : if (!out_w || !out_h)
3355 : 0 : goto fallback;
3356 : :
3357 : : int fidx = 0;
3358 : : struct cached_frame frames[MAX_MIX_FRAMES];
3359 : : float weights[MAX_MIX_FRAMES];
3360 : : float wsum = 0.0;
3361 : :
3362 : : // Garbage collect the cache by evicting all frames from the cache that are
3363 : : // not determined to still be required
3364 [ + + ]: 1788 : for (int i = 0; i < rr->frames.num; i++)
3365 : 1180 : rr->frames.elem[i].evict = true;
3366 : :
3367 : : // Blur frame mixer according to vsync ratio (source / display)
3368 : : struct pl_filter_config mixer;
3369 [ + - ]: 608 : if (params->frame_mixer) {
3370 : 608 : mixer = *params->frame_mixer;
3371 [ - + ]: 608 : mixer.blur = PL_DEF(mixer.blur, 1.0);
3372 [ + + ]: 992 : for (int i = 1; i < images->num_frames; i++) {
3373 [ + + + + ]: 960 : if (images->timestamps[i] >= 0.0 && images->timestamps[i - 1] < 0) {
3374 : 576 : float frame_dur = images->timestamps[i] - images->timestamps[i - 1];
3375 [ - + - - ]: 576 : if (images->vsync_duration > frame_dur && !params->skip_anti_aliasing)
3376 : 0 : mixer.blur *= images->vsync_duration / frame_dur;
3377 : : break;
3378 : : }
3379 : : }
3380 : : }
3381 : :
3382 : : // Traverse the input frames and determine/prepare the ones we need
3383 [ + - + + ]: 620 : bool single_frame = !params->frame_mixer || images->num_frames == 1;
3384 : 616 : retry:
3385 [ + + ]: 2360 : for (int i = 0; i < images->num_frames; i++) {
3386 : 1752 : uint64_t sig = images->signatures[i];
3387 : 1752 : float rts = images->timestamps[i];
3388 : 1752 : const struct pl_frame *img = images->frames[i];
3389 : 1752 : PL_TRACE(rr, "Considering image with signature 0x%llx, rts %f",
3390 : : (unsigned long long) sig, rts);
3391 : :
3392 : : // Combining images with different rotations is basically unfeasible
3393 [ - + ]: 1752 : if (pl_rotation_normalize(img->rotation - refimg->rotation)) {
3394 : 0 : PL_TRACE(rr, " -> Skipping: incompatible rotation");
3395 : 0 : continue;
3396 : : }
3397 : :
3398 : : float weight;
3399 [ + + ]: 1752 : if (single_frame) {
3400 : :
3401 : : // Only render the refimg, ignore others
3402 [ - + ]: 20 : if (img == refimg) {
3403 : : weight = 1.0;
3404 : : } else {
3405 : 0 : PL_TRACE(rr, " -> Skipping: no frame mixer");
3406 : 0 : continue;
3407 : : }
3408 : :
3409 : : // For backwards compatibility, treat !kernel as oversample
3410 [ + - + + ]: 1732 : } else if (!mixer.kernel || mixer.kernel == &pl_filter_function_oversample) {
3411 : :
3412 : : // Compute the visible interval [rts, end] of this frame
3413 [ + + ]: 776 : float end = i+1 < images->num_frames ? images->timestamps[i+1] : INFINITY;
3414 [ + + - + ]: 776 : if (rts > images->vsync_duration || end < 0.0) {
3415 : 156 : PL_TRACE(rr, " -> Skipping: no intersection with vsync");
3416 : 156 : continue;
3417 : : } else {
3418 [ + + ]: 620 : rts = PL_MAX(rts, 0.0);
3419 [ + + ]: 620 : end = PL_MIN(end, images->vsync_duration);
3420 [ - + ]: 620 : pl_assert(end >= rts);
3421 : : }
3422 : :
3423 : : // Weight is the fraction of vsync interval that frame is visible
3424 : 620 : weight = (end - rts) / images->vsync_duration;
3425 : 620 : PL_TRACE(rr, " -> Frame [%f, %f] intersects [%f, %f] = weight %f",
3426 : : rts, end, 0.0, images->vsync_duration, weight);
3427 : :
3428 [ - + ]: 620 : if (weight < mixer.kernel->params[0]) {
3429 : 0 : PL_TRACE(rr, " (culling due to threshold)");
3430 : : weight = 0.0;
3431 : : }
3432 : :
3433 : : } else {
3434 : :
3435 : : const float radius = pl_filter_radius_bound(&mixer);
3436 [ + + ]: 956 : if (fabsf(rts) >= radius) {
3437 : 208 : PL_TRACE(rr, " -> Skipping: outside filter radius (%f)", radius);
3438 : 208 : continue;
3439 : : }
3440 : :
3441 : : // Weight is directly sampled from the filter
3442 : 748 : weight = pl_filter_sample(&mixer, rts);
3443 : 748 : PL_TRACE(rr, " -> Filter offset %f = weight %f", rts, weight);
3444 : :
3445 : : }
3446 : :
3447 : : struct cached_frame *f = NULL;
3448 [ + + ]: 3304 : for (int j = 0; j < rr->frames.num; j++) {
3449 [ + + ]: 2780 : if (rr->frames.elem[j].signature == sig) {
3450 : : f = &rr->frames.elem[j];
3451 : 864 : f->evict = false;
3452 : 864 : break;
3453 : : }
3454 : : }
3455 : :
3456 : : // Skip frames with negligible contributions. Do this after the loop
3457 : : // above to make sure these frames don't get evicted just yet, and
3458 : : // also exclude the reference image from this optimization to ensure
3459 : : // that we always have at least one frame.
3460 : : const float cutoff = 1e-3;
3461 [ + + + + ]: 1388 : if (fabsf(weight) <= cutoff && img != refimg) {
3462 : 360 : PL_TRACE(rr, " -> Skipping: weight (%f) below threshold (%f)",
3463 : : weight, cutoff);
3464 : 360 : continue;
3465 : : }
3466 : :
3467 [ + + + - : 1028 : bool skip_cache = single_frame && (params->skip_caching_single_frame || par_info.trivial);
+ - ]
3468 [ + + ]: 1028 : if (!f && skip_cache) {
3469 : 8 : PL_TRACE(rr, "Single frame not found in cache, bypassing");
3470 : 8 : goto fallback;
3471 : : }
3472 : :
3473 [ + + ]: 1020 : if (!f) {
3474 : : // Signature does not exist in the cache at all yet,
3475 : : // so grow the cache by this entry.
3476 [ + + - + : 320 : PL_ARRAY_GROW(rr, rr->frames);
- + ]
3477 : 320 : f = &rr->frames.elem[rr->frames.num++];
3478 : 320 : *f = (struct cached_frame) {
3479 : : .signature = sig,
3480 : : };
3481 : : }
3482 : :
3483 : : // Check to see if we can blindly reuse this cache entry. This is the
3484 : : // case if either the params are compatible, or the user doesn't care
3485 : 1020 : bool can_reuse = f->tex;
3486 [ + + ]: 1020 : bool strict_reuse = skip_cache || single_frame ||
3487 [ + - ]: 1008 : !params->preserve_mixing_cache;
3488 [ + + ]: 1020 : if (can_reuse && strict_reuse) {
3489 : 700 : can_reuse = f->tex->params.w == out_w &&
3490 [ - + ]: 700 : f->tex->params.h == out_h &&
3491 [ - + - + : 700 : pl_rect2d_eq(f->crop, img->crop) &&
- + - + ]
3492 [ - + - + ]: 1400 : f->params_hash == par_info.hash &&
3493 [ + - - + ]: 2100 : pl_color_space_equal(&f->color, &target->color) &&
3494 : 700 : pl_icc_profile_equal(&f->profile, &target->profile);
3495 : : }
3496 : :
3497 [ - + ]: 1020 : if (!can_reuse && skip_cache) {
3498 : 0 : PL_TRACE(rr, "Single frame cache entry invalid, bypassing");
3499 : 0 : goto fallback;
3500 : : }
3501 : :
3502 [ + + ]: 1020 : if (!can_reuse) {
3503 : : // If we can't reuse the entry, we need to re-render this frame
3504 : 320 : PL_TRACE(rr, " -> Cached texture missing or invalid.. (re)creating");
3505 [ + - ]: 320 : if (!f->tex) {
3506 [ + + ]: 320 : if (PL_ARRAY_POP(rr->frame_fbos, &f->tex))
3507 : 304 : pl_tex_invalidate(rr->gpu, f->tex);
3508 : : }
3509 : :
3510 : 320 : bool ok = pl_tex_recreate(rr->gpu, &f->tex, pl_tex_params(
3511 : : .w = out_w,
3512 : : .h = out_h,
3513 : : .format = pass.fbofmt[4],
3514 : : .sampleable = true,
3515 : : .renderable = true,
3516 : : .blit_dst = pass.fbofmt[4]->caps & PL_FMT_CAP_BLITTABLE,
3517 : : .storable = pass.fbofmt[4]->caps & PL_FMT_CAP_STORABLE,
3518 : : ));
3519 : :
3520 [ - + ]: 320 : if (!ok) {
3521 : 0 : PL_ERR(rr, "Could not create intermediate texture for "
3522 : : "frame mixing.. disabling!");
3523 : 0 : rr->errors |= PL_RENDER_ERR_FRAME_MIXING;
3524 : 0 : goto fallback;
3525 : : }
3526 : :
3527 : 320 : struct pass_state inter_pass = {
3528 : : .rr = rr,
3529 : 320 : .params = pass.params,
3530 : : .image = *img,
3531 : : .target = *ptarget,
3532 : : .info.stage = PL_RENDER_STAGE_FRAME,
3533 : : .acquired = pass.acquired,
3534 : : };
3535 : :
3536 : : // Render a single frame up to `pass_output_target`
3537 : : memcpy(inter_pass.fbofmt, pass.fbofmt, sizeof(pass.fbofmt));
3538 [ - + ]: 320 : if (!pass_init(&inter_pass, true))
3539 : 0 : goto fail;
3540 : :
3541 : 320 : pass_begin_frame(&inter_pass);
3542 [ - + ]: 320 : if (!(ok = pass_read_image(&inter_pass)))
3543 : 0 : goto inter_pass_error;
3544 [ - + ]: 320 : if (!(ok = pass_scale_main(&inter_pass)))
3545 : 0 : goto inter_pass_error;
3546 : 320 : pass_convert_colors(&inter_pass);
3547 : :
3548 [ - + ]: 320 : pl_assert(inter_pass.img.sh); // guaranteed by `pass_convert_colors`
3549 : 320 : pl_shader_set_alpha(inter_pass.img.sh, &inter_pass.img.repr,
3550 : : PL_ALPHA_PREMULTIPLIED); // for frame mixing
3551 : :
3552 [ + - - + ]: 320 : pl_assert(inter_pass.img.w == out_w &&
3553 : : inter_pass.img.h == out_h);
3554 : :
3555 : 320 : ok = pl_dispatch_finish(rr->dp, pl_dispatch_params(
3556 : : .shader = &inter_pass.img.sh,
3557 : : .target = f->tex,
3558 : : ));
3559 [ - + ]: 320 : if (!ok)
3560 : 0 : goto inter_pass_error;
3561 : :
3562 : 320 : float sx = out_w / pl_rect_w(inter_pass.dst_rect),
3563 : 320 : sy = out_h / pl_rect_h(inter_pass.dst_rect);
3564 : :
3565 : 320 : pl_transform2x2 shift = {
3566 : : .mat.m = {{ sx, 0, }, { 0, sy, }},
3567 : : .c = {
3568 : 320 : -sx * inter_pass.dst_rect.x0,
3569 : 320 : -sy * inter_pass.dst_rect.y0
3570 : : },
3571 : : };
3572 : :
3573 [ + - ]: 320 : if (inter_pass.rotation % PL_ROTATION_180 == PL_ROTATION_90) {
3574 : 320 : PL_SWAP(shift.mat.m[0][0], shift.mat.m[0][1]);
3575 : 320 : PL_SWAP(shift.mat.m[1][0], shift.mat.m[1][1]);
3576 : : }
3577 : :
3578 : 320 : draw_overlays(&inter_pass, f->tex, inter_pass.img.comps, NULL,
3579 : : inter_pass.image.overlays,
3580 : : inter_pass.image.num_overlays,
3581 : : inter_pass.img.color,
3582 : : inter_pass.img.repr,
3583 : : &shift);
3584 : :
3585 : 320 : f->params_hash = par_info.hash;
3586 : 320 : f->crop = img->crop;
3587 : 320 : f->color = inter_pass.img.color;
3588 : 320 : f->comps = inter_pass.img.comps;
3589 : 320 : f->profile = target->profile;
3590 : : // fall through
3591 : :
3592 : 320 : inter_pass_error:
3593 : 320 : inter_pass.acquired.target = false; // don't release target
3594 : 320 : pass_uninit(&inter_pass);
3595 [ - + ]: 320 : if (!ok)
3596 : 0 : goto fail;
3597 : : }
3598 : :
3599 [ - + ]: 1020 : pl_assert(fidx < MAX_MIX_FRAMES);
3600 : 1020 : frames[fidx] = *f;
3601 : 1020 : weights[fidx] = weight;
3602 : 1020 : wsum += weight;
3603 : 1020 : fidx++;
3604 : : }
3605 : :
3606 : : // Evict the frames we *don't* need
3607 [ + + ]: 2108 : for (int i = 0; i < rr->frames.num; ) {
3608 [ + + ]: 1500 : if (rr->frames.elem[i].evict) {
3609 : 316 : PL_TRACE(rr, "Evicting frame with signature %llx from cache",
3610 : : (unsigned long long) rr->frames.elem[i].signature);
3611 [ + + - + : 316 : PL_ARRAY_APPEND(rr, rr->frame_fbos, rr->frames.elem[i].tex);
- + ]
3612 [ - + - - : 316 : PL_ARRAY_REMOVE_AT(rr->frames, i);
- + ]
3613 : 316 : continue;
3614 : : } else {
3615 : 1184 : i++;
3616 : : }
3617 : : }
3618 : :
3619 : : // If we got back no frames, retry with ZOH semantics
3620 [ + + ]: 608 : if (!fidx) {
3621 [ - + ]: 8 : pl_assert(!single_frame);
3622 : : single_frame = true;
3623 : 8 : goto retry;
3624 : : }
3625 : :
3626 : : // Sample and mix the output color
3627 : 600 : pass_begin_frame(&pass);
3628 : 600 : pass.info.count = fidx;
3629 [ - + ]: 600 : pl_assert(fidx > 0);
3630 : :
3631 : 600 : pl_shader sh = pl_dispatch_begin(rr->dp);
3632 [ + + ]: 816 : sh_describef(sh, "frame mixing (%d frame%s)", fidx, fidx > 1 ? "s" : "");
3633 : 600 : sh->output = PL_SHADER_SIG_COLOR;
3634 : 600 : sh->output_w = out_w;
3635 : 600 : sh->output_h = out_h;
3636 : :
3637 : 600 : GLSL("vec4 color; \n"
3638 : : "// pl_render_image_mix \n"
3639 : : "{ \n"
3640 : : "vec4 mix_color = vec4(0.0); \n");
3641 : :
3642 : : int comps = 0;
3643 [ + + ]: 1620 : for (int i = 0; i < fidx; i++) {
3644 : 1020 : const struct pl_tex_params *tpars = &frames[i].tex->params;
3645 : :
3646 : : // Use linear sampling if desired and possible
3647 : : enum pl_tex_sample_mode sample_mode = PL_TEX_SAMPLE_NEAREST;
3648 [ + - + - ]: 1020 : if ((tpars->w != out_w || tpars->h != out_h) &&
3649 [ # # ]: 0 : (tpars->format->caps & PL_FMT_CAP_LINEAR))
3650 : : {
3651 : : sample_mode = PL_TEX_SAMPLE_LINEAR;
3652 : : }
3653 : :
3654 : 1020 : ident_t pos, tex = sh_bind(sh, frames[i].tex, PL_TEX_ADDRESS_CLAMP,
3655 : : sample_mode, "frame", NULL, &pos, NULL);
3656 : :
3657 : 1020 : GLSL("color = textureLod("$", "$", 0.0); \n", tex, pos);
3658 : :
3659 : : // Note: This ignores differences in ICC profile, which we decide to
3660 : : // just simply not care about. Doing that properly would require
3661 : : // converting between different image profiles, and the headache of
3662 : : // finagling that state is just not worth it because this is an
3663 : : // exceptionally unlikely hypothetical.
3664 : : //
3665 : : // This also ignores differences in HDR metadata, which we deliberately
3666 : : // ignore because it causes aggressive shader recompilation.
3667 : 1020 : struct pl_color_space frame_csp = frames[i].color;
3668 : 1020 : struct pl_color_space mix_csp = target->color;
3669 : 1020 : frame_csp.hdr = mix_csp.hdr = (struct pl_hdr_metadata) {0};
3670 : 1020 : pl_shader_color_map_ex(sh, NULL, pl_color_map_args(frame_csp, mix_csp));
3671 : :
3672 : 1020 : float weight = weights[i] / wsum;
3673 : 1020 : GLSL("mix_color += vec4("$") * color; \n", SH_FLOAT_DYN(weight));
3674 : 1020 : comps = PL_MAX(comps, frames[i].comps);
3675 : : }
3676 : :
3677 : 600 : GLSL("color = mix_color; \n"
3678 : : "} \n");
3679 : :
3680 : : // Dispatch this to the destination
3681 : 600 : pass.img = (struct img) {
3682 : : .sh = sh,
3683 : : .w = out_w,
3684 : : .h = out_h,
3685 : : .comps = comps,
3686 : : .color = target->color,
3687 : : .repr = {
3688 : : .sys = PL_COLOR_SYSTEM_RGB,
3689 : : .levels = PL_COLOR_LEVELS_PC,
3690 [ + - ]: 600 : .alpha = comps >= 4 ? PL_ALPHA_PREMULTIPLIED : PL_ALPHA_NONE,
3691 : : },
3692 : : };
3693 : :
3694 [ - + ]: 600 : if (!pass_output_target(&pass))
3695 : 0 : goto fallback;
3696 : :
3697 : 600 : pass_uninit(&pass);
3698 : 600 : return true;
3699 : :
3700 : : fail:
3701 : 0 : PL_ERR(rr, "Could not render image for frame mixing.. disabling!");
3702 : 0 : rr->errors |= PL_RENDER_ERR_FRAME_MIXING;
3703 : : // fall through
3704 : :
3705 : 8 : fallback:
3706 : 8 : pass_uninit(&pass);
3707 : 8 : return pl_render_image(rr, refimg, ptarget, params);
3708 : :
3709 : : error: // for parameter validation failures
3710 : : return false;
3711 : : }
3712 : :
3713 : 8 : void pl_frames_infer_mix(pl_renderer rr, const struct pl_frame_mix *mix,
3714 : : struct pl_frame *target, struct pl_frame *out_ref)
3715 : : {
3716 : 8 : struct pass_state pass = {
3717 : : .rr = rr,
3718 : : .target = *target,
3719 : : };
3720 : :
3721 : 8 : const struct pl_frame *refimg = pl_frame_mix_nearest(mix);
3722 [ + + ]: 8 : if (refimg) {
3723 : 4 : pass.image = *refimg;
3724 : : } else {
3725 : 4 : pass.src_ref = -1;
3726 : : }
3727 : :
3728 : 8 : pass_fix_frames(&pass);
3729 : 8 : *target = pass.target;
3730 [ + - ]: 8 : if (out_ref)
3731 : 8 : *out_ref = pass.image;
3732 : 8 : }
3733 : :
3734 : 5 : void pl_frame_set_chroma_location(struct pl_frame *frame,
3735 : : enum pl_chroma_location chroma_loc)
3736 : : {
3737 : 5 : pl_tex ref = frame->planes[frame_ref(frame)].texture;
3738 : :
3739 [ - + ]: 5 : if (ref) {
3740 : : // Texture dimensions are already known, so apply the chroma location
3741 : : // only to subsampled planes
3742 : 5 : int ref_w = ref->params.w, ref_h = ref->params.h;
3743 : :
3744 [ + + ]: 20 : for (int i = 0; i < frame->num_planes; i++) {
3745 : : struct pl_plane *plane = &frame->planes[i];
3746 : 15 : pl_tex tex = plane->texture;
3747 [ + + - + ]: 15 : bool subsampled = tex->params.w < ref_w || tex->params.h < ref_h;
3748 : : if (subsampled)
3749 : 10 : pl_chroma_location_offset(chroma_loc, &plane->shift_x, &plane->shift_y);
3750 : : }
3751 : : } else {
3752 : : // Texture dimensions are not yet known, so apply the chroma location
3753 : : // to all chroma planes, regardless of subsampling
3754 [ # # ]: 0 : for (int i = 0; i < frame->num_planes; i++) {
3755 : 0 : struct pl_plane *plane = &frame->planes[i];
3756 [ # # ]: 0 : if (detect_plane_type(plane, &frame->repr) == PLANE_CHROMA)
3757 : 0 : pl_chroma_location_offset(chroma_loc, &plane->shift_x, &plane->shift_y);
3758 : : }
3759 : : }
3760 : 5 : }
3761 : :
3762 : 50 : void pl_frame_from_swapchain(struct pl_frame *out_frame,
3763 : : const struct pl_swapchain_frame *frame)
3764 : : {
3765 : 50 : pl_tex fbo = frame->fbo;
3766 : 50 : int num_comps = fbo->params.format->num_components;
3767 [ + + ]: 50 : if (frame->color_repr.alpha == PL_ALPHA_NONE)
3768 : 40 : num_comps = PL_MIN(num_comps, 3);
3769 : :
3770 : 50 : *out_frame = (struct pl_frame) {
3771 : : .num_planes = 1,
3772 : : .planes = {{
3773 : : .texture = fbo,
3774 : 50 : .flipped = frame->flipped,
3775 : : .components = num_comps,
3776 : : .component_mapping = {0, 1, 2, 3},
3777 : : }},
3778 : 50 : .crop = { 0, 0, fbo->params.w, fbo->params.h },
3779 : : .repr = frame->color_repr,
3780 : : .color = frame->color_space,
3781 : : };
3782 : 50 : }
3783 : :
3784 : 1077 : bool pl_frame_is_cropped(const struct pl_frame *frame)
3785 : : {
3786 [ + + ]: 1077 : int x0 = roundf(PL_MIN(frame->crop.x0, frame->crop.x1)),
3787 [ + + ]: 1077 : y0 = roundf(PL_MIN(frame->crop.y0, frame->crop.y1)),
3788 [ + + ]: 1077 : x1 = roundf(PL_MAX(frame->crop.x0, frame->crop.x1)),
3789 [ + + ]: 1077 : y1 = roundf(PL_MAX(frame->crop.y0, frame->crop.y1));
3790 : :
3791 : 1077 : pl_tex ref = frame->planes[frame_ref(frame)].texture;
3792 [ - + ]: 1077 : pl_assert(ref);
3793 : :
3794 [ - + ]: 1077 : if (!x0 && !x1)
3795 : 0 : x1 = ref->params.w;
3796 [ - + ]: 1077 : if (!y0 && !y1)
3797 : 0 : y1 = ref->params.h;
3798 : :
3799 [ + - + + : 1077 : return x0 > 0 || y0 > 0 || x1 < ref->params.w || y1 < ref->params.h;
- + ]
3800 : : }
3801 : :
3802 : 0 : void pl_frame_clear_tiles(pl_gpu gpu, const struct pl_frame *frame,
3803 : : const float tile_colors[2][3], int tile_size)
3804 : : {
3805 : 0 : struct pl_color_repr repr = frame->repr;
3806 : 0 : pl_transform3x3 tr = pl_color_repr_decode(&repr, NULL);
3807 : 0 : pl_transform3x3_invert(&tr);
3808 : :
3809 : : float encoded[2][3];
3810 : : memcpy(encoded, tile_colors, sizeof(encoded));
3811 : 0 : pl_transform3x3_apply(&tr, encoded[0]);
3812 : 0 : pl_transform3x3_apply(&tr, encoded[1]);
3813 : :
3814 : 0 : pl_tex ref = frame->planes[frame_ref(frame)].texture;
3815 : :
3816 [ # # ]: 0 : for (int p = 0; p < frame->num_planes; p++) {
3817 : : const struct pl_plane *plane = &frame->planes[p];
3818 : 0 : float tiles[2][3] = {0};
3819 [ # # ]: 0 : for (int c = 0; c < plane->components; c++) {
3820 : 0 : int ch = plane->component_mapping[c];
3821 [ # # ]: 0 : if (ch >= 0 && ch < 3) {
3822 : 0 : tiles[0][c] = encoded[0][plane->component_mapping[c]];
3823 : 0 : tiles[1][c] = encoded[1][plane->component_mapping[c]];
3824 : : }
3825 : : }
3826 : :
3827 : 0 : float rx = (float) plane->texture->params.w / ref->params.w,
3828 : 0 : ry = (float) plane->texture->params.h / ref->params.h;
3829 [ # # ]: 0 : float rrx = rx >= 1 ? roundf(rx) : 1.0 / roundf(1.0 / rx),
3830 [ # # ]: 0 : rry = ry >= 1 ? roundf(ry) : 1.0 / roundf(1.0 / ry);
3831 : 0 : int size_x = tile_size * rrx, size_y = tile_size * rry;
3832 : :
3833 : 0 : pl_dispatch dp = pl_gpu_dispatch(gpu);
3834 : 0 : pl_shader sh = pl_dispatch_begin(dp);
3835 : 0 : sh->output = PL_SHADER_SIG_COLOR;
3836 : 0 : GLSL("// pl_frame_clear_tiles (plane %d) \n"
3837 : : "vec4 color; \n"
3838 : : "vec2 outcoord = gl_FragCoord.xy * vec2("$", "$"); \n"
3839 : : "bvec2 tile = lessThan(fract(outcoord), vec2(0.5)); \n"
3840 : : "color.rgb = tile.x == tile.y ? vec3("$", "$", "$") \n"
3841 : : " : vec3("$", "$", "$"); \n"
3842 : : "color.a = 1.0; \n",
3843 : : p, SH_FLOAT(1.0 / size_x), SH_FLOAT(1.0 / size_y),
3844 : : SH_FLOAT(tiles[0][0]), SH_FLOAT(tiles[0][1]), SH_FLOAT(tiles[0][2]),
3845 : : SH_FLOAT(tiles[1][0]), SH_FLOAT(tiles[1][1]), SH_FLOAT(tiles[1][2]));
3846 : :
3847 : 0 : pl_dispatch_finish(dp, pl_dispatch_params(
3848 : : .shader = &sh,
3849 : : .target = plane->texture,
3850 : : ));
3851 : : }
3852 : 0 : }
3853 : :
3854 : 120 : void pl_frame_clear_rgba(pl_gpu gpu, const struct pl_frame *frame,
3855 : : const float rgba[4])
3856 : : {
3857 : 120 : struct pl_color_repr repr = frame->repr;
3858 : 120 : pl_transform3x3 tr = pl_color_repr_decode(&repr, NULL);
3859 : 120 : pl_transform3x3_invert(&tr);
3860 : :
3861 : 120 : float encoded[3] = { rgba[0], rgba[1], rgba[2] };
3862 : 120 : pl_transform3x3_apply(&tr, encoded);
3863 : :
3864 [ - + ]: 120 : float mult = frame->repr.alpha == PL_ALPHA_PREMULTIPLIED ? rgba[3] : 1.0;
3865 [ + + ]: 240 : for (int p = 0; p < frame->num_planes; p++) {
3866 : : const struct pl_plane *plane = &frame->planes[p];
3867 : 120 : float clear[4] = { 0.0, 0.0, 0.0, rgba[3] };
3868 [ + + ]: 480 : for (int c = 0; c < plane->components; c++) {
3869 : 360 : int ch = plane->component_mapping[c];
3870 [ + - ]: 360 : if (ch >= 0 && ch < 3)
3871 : 360 : clear[c] = mult * encoded[plane->component_mapping[c]];
3872 : : }
3873 : :
3874 : 120 : pl_tex_clear(gpu, plane->texture, clear);
3875 : : }
3876 : 120 : }
3877 : :
3878 : 475 : struct pl_render_errors pl_renderer_get_errors(pl_renderer rr)
3879 : : {
3880 : 475 : return (struct pl_render_errors) {
3881 : 475 : .errors = rr->errors,
3882 : 475 : .disabled_hooks = rr->disabled_hooks.elem,
3883 : 475 : .num_disabled_hooks = rr->disabled_hooks.num,
3884 : : };
3885 : : }
3886 : :
3887 : 2 : void pl_renderer_reset_errors(pl_renderer rr,
3888 : : const struct pl_render_errors *errors)
3889 : : {
3890 [ - + ]: 2 : if (!errors) {
3891 : : // Reset everything
3892 : 0 : rr->errors = PL_RENDER_ERR_NONE;
3893 : 0 : rr->disabled_hooks.num = 0;
3894 : 0 : return;
3895 : : }
3896 : :
3897 : : // Reset only requested errors
3898 : 2 : rr->errors &= ~errors->errors;
3899 : :
3900 : : // Not clearing hooks
3901 [ + - ]: 2 : if (!(errors->errors & PL_RENDER_ERR_HOOKS))
3902 : 2 : goto done;
3903 : :
3904 : : // Remove all hook signatures
3905 [ # # ]: 0 : if (!errors->num_disabled_hooks) {
3906 : 0 : rr->disabled_hooks.num = 0;
3907 : 0 : goto done;
3908 : : }
3909 : :
3910 : : // At this point we require valid array of hooks
3911 [ # # ]: 0 : if (!errors->disabled_hooks) {
3912 : 0 : assert(errors->disabled_hooks);
3913 : : goto done;
3914 : : }
3915 : :
3916 [ # # ]: 0 : for (int i = 0; i < errors->num_disabled_hooks; i++) {
3917 [ # # ]: 0 : for (int j = 0; j < rr->disabled_hooks.num; j++) {
3918 : : // Remove only requested hook signatures
3919 [ # # ]: 0 : if (rr->disabled_hooks.elem[j] == errors->disabled_hooks[i]) {
3920 [ # # ]: 0 : PL_ARRAY_REMOVE_AT(rr->disabled_hooks, j);
3921 : 0 : break;
3922 : : }
3923 : : }
3924 : : }
3925 : :
3926 : 0 : done:
3927 [ - + ]: 2 : if (rr->disabled_hooks.num)
3928 : 0 : rr->errors |= PL_RENDER_ERR_HOOKS;
3929 : : return;
3930 : : }
|