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 "cache.h"
21 : : #include "colorspace.h"
22 : : #include "shaders.h"
23 : :
24 : : #include <libplacebo/shaders/colorspace.h>
25 : :
26 : 2821 : void pl_shader_set_alpha(pl_shader sh, struct pl_color_repr *repr,
27 : : enum pl_alpha_mode mode)
28 : : {
29 : 2821 : bool src_has_alpha = repr->alpha == PL_ALPHA_INDEPENDENT ||
30 : : repr->alpha == PL_ALPHA_PREMULTIPLIED;
31 : 2821 : bool dst_not_premul = mode == PL_ALPHA_INDEPENDENT ||
32 : 2821 : mode == PL_ALPHA_NONE;
33 : :
34 [ - + - - ]: 2821 : if (repr->alpha == PL_ALPHA_PREMULTIPLIED && dst_not_premul) {
35 : 0 : GLSL("if (color.a > 1e-6) \n"
36 : : " color.rgb /= vec3(color.a); \n");
37 : 0 : repr->alpha = PL_ALPHA_INDEPENDENT;
38 : : }
39 : :
40 [ + + + - ]: 2821 : if (repr->alpha == PL_ALPHA_INDEPENDENT && mode == PL_ALPHA_PREMULTIPLIED) {
41 : 8 : GLSL("color.rgb *= vec3(color.a); \n");
42 : 8 : repr->alpha = PL_ALPHA_PREMULTIPLIED;
43 : : }
44 : :
45 [ - + ]: 2821 : if (src_has_alpha && mode == PL_ALPHA_NONE) {
46 : 0 : GLSL("color.a = 1.0; \n");
47 : 0 : repr->alpha = PL_ALPHA_NONE;
48 : : }
49 : 2821 : }
50 : :
51 : : #ifdef PL_HAVE_DOVI
52 : 4 : static inline void reshape_mmr(pl_shader sh, ident_t mmr, bool single,
53 : : int min_order, int max_order)
54 : : {
55 [ + - ]: 4 : if (single) {
56 : 4 : GLSL("const uint mmr_idx = 0u; \n");
57 : : } else {
58 : 0 : GLSL("uint mmr_idx = uint(coeffs.y); \n");
59 : : }
60 : :
61 [ - + ]: 4 : assert(min_order <= max_order);
62 [ - + ]: 4 : if (min_order < max_order)
63 : 0 : GLSL("uint order = uint(coeffs.w); \n");
64 : :
65 : 4 : GLSL("vec4 sigX; \n"
66 : : "s = coeffs.x; \n"
67 : : "sigX.xyz = sig.xxy * sig.yzz; \n"
68 : : "sigX.w = sigX.x * sig.z; \n"
69 : : "s += dot("$"[mmr_idx + 0].xyz, sig); \n"
70 : : "s += dot("$"[mmr_idx + 1], sigX); \n",
71 : : mmr, mmr);
72 : :
73 [ + - ]: 4 : if (max_order >= 2) {
74 [ - + ]: 4 : if (min_order < 2)
75 : 0 : GLSL("if (order >= 2) { \n");
76 : :
77 : 4 : GLSL("vec3 sig2 = sig * sig; \n"
78 : : "vec4 sigX2 = sigX * sigX; \n"
79 : : "s += dot("$"[mmr_idx + 2].xyz, sig2); \n"
80 : : "s += dot("$"[mmr_idx + 3], sigX2); \n",
81 : : mmr, mmr);
82 : :
83 [ + - ]: 4 : if (max_order == 3) {
84 [ - + ]: 4 : if (min_order < 3)
85 : 0 : GLSL("if (order >= 3 { \n");
86 : :
87 : 4 : GLSL("s += dot("$"[mmr_idx + 4].xyz, sig2 * sig); \n"
88 : : "s += dot("$"[mmr_idx + 5], sigX2 * sigX); \n",
89 : : mmr, mmr);
90 : :
91 [ - + ]: 4 : if (min_order < 3)
92 : 0 : GLSL("} \n");
93 : : }
94 : :
95 [ # # ]: 0 : if (min_order < 2)
96 : 0 : GLSL("} \n");
97 : : }
98 : 4 : }
99 : :
100 : : static inline void reshape_poly(pl_shader sh)
101 : : {
102 : 2 : GLSL("s = (coeffs.z * s + coeffs.y) * s + coeffs.x; \n");
103 : 2 : }
104 : : #endif
105 : :
106 : 2 : void pl_shader_dovi_reshape(pl_shader sh, const struct pl_dovi_metadata *data)
107 : : {
108 : : #ifdef PL_HAVE_DOVI
109 [ + - - + ]: 2 : if (!sh_require(sh, PL_SHADER_SIG_COLOR, 0, 0) || !data)
110 : 0 : return;
111 : :
112 : 2 : sh_describe(sh, "reshaping");
113 : 2 : GLSL("// pl_shader_reshape \n"
114 : : "{ \n"
115 : : "vec3 sig; \n"
116 : : "vec4 coeffs; \n"
117 : : "float s; \n"
118 : : "sig = clamp(color.rgb, 0.0, 1.0); \n");
119 : :
120 : : float coeffs_data[8][4];
121 : : float mmr_packed_data[8*6][4];
122 : :
123 [ + + ]: 8 : for (int c = 0; c < 3; c++) {
124 : : const struct pl_reshape_data *comp = &data->comp[c];
125 [ - + ]: 6 : if (!comp->num_pivots)
126 : 0 : continue;
127 : :
128 [ - + ]: 6 : pl_assert(comp->num_pivots >= 2 && comp->num_pivots <= 9);
129 : 6 : GLSL("s = sig[%d]; \n", c);
130 : :
131 : : // Prepare coefficients for GPU
132 : : bool has_poly = false, has_mmr = false, mmr_single = true;
133 : : int mmr_idx = 0, min_order = 3, max_order = 1;
134 : : memset(coeffs_data, 0, sizeof(coeffs_data));
135 [ + + ]: 26 : for (int i = 0; i < comp->num_pivots - 1; i++) {
136 [ + + - ]: 20 : switch (comp->method[i]) {
137 : 16 : case 0: // polynomial
138 : : has_poly = true;
139 : 16 : coeffs_data[i][3] = 0.0; // order=0 signals polynomial
140 [ + + ]: 64 : for (int k = 0; k < 3; k++)
141 : 48 : coeffs_data[i][k] = comp->poly_coeffs[i][k];
142 : : break;
143 : :
144 : 4 : case 1:
145 : 4 : min_order = PL_MIN(min_order, comp->mmr_order[i]);
146 : 4 : max_order = PL_MAX(max_order, comp->mmr_order[i]);
147 : 4 : mmr_single = !has_mmr;
148 : : has_mmr = true;
149 : 4 : coeffs_data[i][3] = (float) comp->mmr_order[i];
150 : 4 : coeffs_data[i][0] = comp->mmr_constant[i];
151 : 4 : coeffs_data[i][1] = (float) mmr_idx;
152 [ + + ]: 16 : for (int j = 0; j < comp->mmr_order[i]; j++) {
153 : : // store weights per order as two packed vec4s
154 : 12 : float *mmr = &mmr_packed_data[mmr_idx][0];
155 : 12 : mmr[0] = comp->mmr_coeffs[i][j][0];
156 : 12 : mmr[1] = comp->mmr_coeffs[i][j][1];
157 : 12 : mmr[2] = comp->mmr_coeffs[i][j][2];
158 : 12 : mmr[3] = 0.0; // unused
159 : 12 : mmr[4] = comp->mmr_coeffs[i][j][3];
160 : 12 : mmr[5] = comp->mmr_coeffs[i][j][4];
161 : 12 : mmr[6] = comp->mmr_coeffs[i][j][5];
162 : 12 : mmr[7] = comp->mmr_coeffs[i][j][6];
163 : 12 : mmr_idx += 2;
164 : : }
165 : : break;
166 : :
167 : : default:
168 : 0 : pl_unreachable();
169 : : }
170 : : }
171 : :
172 [ + + ]: 6 : if (comp->num_pivots > 2) {
173 : :
174 : : // Skip the (irrelevant) lower and upper bounds
175 : : float pivots_data[7];
176 : 2 : memcpy(pivots_data, comp->pivots + 1,
177 : 2 : (comp->num_pivots - 2) * sizeof(pivots_data[0]));
178 : :
179 : : // Fill the remainder with a quasi-infinite sentinel pivot
180 [ - + ]: 2 : for (int i = comp->num_pivots - 2; i < PL_ARRAY_SIZE(pivots_data); i++)
181 : 0 : pivots_data[i] = 1e9f;
182 : :
183 : 2 : ident_t pivots = sh_var(sh, (struct pl_shader_var) {
184 : : .data = pivots_data,
185 : : .var = {
186 : : .name = "pivots",
187 : : .type = PL_VAR_FLOAT,
188 : : .dim_v = 1,
189 : : .dim_m = 1,
190 : : .dim_a = PL_ARRAY_SIZE(pivots_data),
191 : : },
192 : : });
193 : :
194 : 2 : ident_t coeffs = sh_var(sh, (struct pl_shader_var) {
195 : : .data = coeffs_data,
196 : : .var = {
197 : : .name = "coeffs",
198 : : .type = PL_VAR_FLOAT,
199 : : .dim_v = 4,
200 : : .dim_m = 1,
201 : : .dim_a = PL_ARRAY_SIZE(coeffs_data),
202 : : },
203 : : });
204 : :
205 : : // Efficiently branch into the correct set of coefficients
206 : 2 : GLSL("#define test(i) bvec4(s >= "$"[i]) \n"
207 : : "#define coef(i) "$"[i] \n"
208 : : "coeffs = mix(mix(mix(coef(0), coef(1), test(0)), \n"
209 : : " mix(coef(2), coef(3), test(2)), \n"
210 : : " test(1)), \n"
211 : : " mix(mix(coef(4), coef(5), test(4)), \n"
212 : : " mix(coef(6), coef(7), test(6)), \n"
213 : : " test(5)), \n"
214 : : " test(3)); \n"
215 : : "#undef test \n"
216 : : "#undef coef \n",
217 : : pivots, coeffs);
218 : :
219 : : } else {
220 : :
221 : : // No need for a single pivot, just set the coeffs directly
222 : 4 : GLSL("coeffs = "$"; \n", sh_var(sh, (struct pl_shader_var) {
223 : : .var = pl_var_vec4("coeffs"),
224 : : .data = coeffs_data,
225 : : }));
226 : :
227 : : }
228 : :
229 : : ident_t mmr = NULL_IDENT;
230 [ + + ]: 6 : if (has_mmr) {
231 : 4 : mmr = sh_var(sh, (struct pl_shader_var) {
232 : : .data = mmr_packed_data,
233 : : .var = {
234 : : .name = "mmr",
235 : : .type = PL_VAR_FLOAT,
236 : : .dim_v = 4,
237 : : .dim_m = 1,
238 : : .dim_a = mmr_idx,
239 : : },
240 : : });
241 : : }
242 : :
243 [ - + ]: 6 : if (has_mmr && has_poly) {
244 : 0 : GLSL("if (coeffs.w == 0.0) { \n");
245 : : reshape_poly(sh);
246 : 0 : GLSL("} else { \n");
247 : 0 : reshape_mmr(sh, mmr, mmr_single, min_order, max_order);
248 : 0 : GLSL("} \n");
249 [ + + ]: 6 : } else if (has_poly) {
250 : : reshape_poly(sh);
251 : : } else {
252 [ - + ]: 4 : assert(has_mmr);
253 : 4 : GLSL("{ \n");
254 : 4 : reshape_mmr(sh, mmr, mmr_single, min_order, max_order);
255 : 4 : GLSL("} \n");
256 : : }
257 : :
258 : 12 : ident_t lo = sh_var(sh, (struct pl_shader_var) {
259 : 6 : .var = pl_var_float("lo"),
260 : 6 : .data = &comp->pivots[0],
261 : : });
262 : 12 : ident_t hi = sh_var(sh, (struct pl_shader_var) {
263 : 6 : .var = pl_var_float("hi"),
264 : 6 : .data = &comp->pivots[comp->num_pivots - 1],
265 : : });
266 : 6 : GLSL("color[%d] = clamp(s, "$", "$"); \n", c, lo, hi);
267 : : }
268 : :
269 : 2 : GLSL("} \n");
270 : : #else
271 : : SH_FAIL(sh, "libplacebo was compiled without support for dolbyvision reshaping");
272 : : #endif
273 : : }
274 : :
275 : 829 : void pl_shader_decode_color(pl_shader sh, struct pl_color_repr *repr,
276 : : const struct pl_color_adjustment *params)
277 : : {
278 [ - + ]: 829 : if (!sh_require(sh, PL_SHADER_SIG_COLOR, 0, 0))
279 : 0 : return;
280 : :
281 : 829 : sh_describe(sh, "color decoding");
282 : 829 : GLSL("// pl_shader_decode_color \n"
283 : : "{ \n");
284 : :
285 : : // Do this first because the following operations are potentially nonlinear
286 : 829 : pl_shader_set_alpha(sh, repr, PL_ALPHA_INDEPENDENT);
287 : :
288 [ + + ]: 829 : if (repr->sys == PL_COLOR_SYSTEM_XYZ ||
289 : : repr->sys == PL_COLOR_SYSTEM_DOLBYVISION)
290 : : {
291 : 4 : ident_t scale = SH_FLOAT(pl_color_repr_normalize(repr));
292 : 4 : GLSL("color.rgb *= vec3("$"); \n", scale);
293 : : }
294 : :
295 [ + + ]: 829 : if (repr->sys == PL_COLOR_SYSTEM_XYZ) {
296 : 2 : pl_shader_linearize(sh, &(struct pl_color_space) {
297 : : .transfer = PL_COLOR_TRC_ST428,
298 : : });
299 : : }
300 : :
301 [ + + ]: 829 : if (repr->sys == PL_COLOR_SYSTEM_DOLBYVISION)
302 : 2 : pl_shader_dovi_reshape(sh, repr->dovi);
303 : :
304 : 829 : enum pl_color_system orig_sys = repr->sys;
305 : 829 : pl_transform3x3 tr = pl_color_repr_decode(repr, params);
306 : :
307 [ + + ]: 829 : if (memcmp(&tr, &pl_transform3x3_identity, sizeof(tr))) {
308 : 807 : ident_t cmat = sh_var(sh, (struct pl_shader_var) {
309 : 807 : .var = pl_var_mat3("cmat"),
310 : 807 : .data = PL_TRANSPOSE_3X3(tr.mat.m),
311 : : });
312 : :
313 : 807 : ident_t cmat_c = sh_var(sh, (struct pl_shader_var) {
314 : 807 : .var = pl_var_vec3("cmat_c"),
315 : : .data = tr.c,
316 : : });
317 : :
318 : 807 : GLSL("color.rgb = "$" * color.rgb + "$"; \n", cmat, cmat_c);
319 : : }
320 : :
321 [ + + + + : 829 : switch (orig_sys) {
- + - ]
322 : : case PL_COLOR_SYSTEM_BT_2020_C:
323 : : // Conversion for C'rcY'cC'bc via the BT.2020 CL system:
324 : : // C'bc = (B'-Y'c) / 1.9404 | C'bc <= 0
325 : : // = (B'-Y'c) / 1.5816 | C'bc > 0
326 : : //
327 : : // C'rc = (R'-Y'c) / 1.7184 | C'rc <= 0
328 : : // = (R'-Y'c) / 0.9936 | C'rc > 0
329 : : //
330 : : // as per the BT.2020 specification, table 4. This is a non-linear
331 : : // transformation because (constant) luminance receives non-equal
332 : : // contributions from the three different channels.
333 : 2 : GLSL("// constant luminance conversion \n"
334 : : "color.br = color.br * mix(vec2(1.5816, 0.9936), \n"
335 : : " vec2(1.9404, 1.7184), \n"
336 : : " lessThanEqual(color.br, vec2(0.0))) \n"
337 : : " + color.gg; \n");
338 : : // Expand channels to camera-linear light. This shader currently just
339 : : // assumes everything uses the BT.2020 12-bit gamma function, since the
340 : : // difference between 10 and 12-bit is negligible for anything other
341 : : // than 12-bit content.
342 : 2 : GLSL("vec3 lin = mix(color.rgb * vec3(1.0/4.5), \n"
343 : : " pow((color.rgb + vec3(0.0993))*vec3(1.0/1.0993), \n"
344 : : " vec3(1.0/0.45)), \n"
345 : : " lessThanEqual(vec3(0.08145), color.rgb)); \n");
346 : : // Calculate the green channel from the expanded RYcB, and recompress to G'
347 : : // The BT.2020 specification says Yc = 0.2627*R + 0.6780*G + 0.0593*B
348 : 2 : GLSL("color.g = (lin.g - 0.2627*lin.r - 0.0593*lin.b)*1.0/0.6780; \n"
349 : : "color.g = mix(color.g * 4.5, \n"
350 : : " 1.0993 * pow(color.g, 0.45) - 0.0993, \n"
351 : : " 0.0181 <= color.g); \n");
352 : : break;
353 : :
354 : : case PL_COLOR_SYSTEM_BT_2100_PQ:;
355 : : // Conversion process from the spec:
356 : : //
357 : : // 1. L'M'S' = cmat * ICtCp
358 : : // 2. LMS = linearize(L'M'S') (EOTF for PQ, inverse OETF for HLG)
359 : : // 3. RGB = lms2rgb * LMS
360 : : //
361 : : // After this we need to invert step 2 to arrive at non-linear RGB.
362 : : // (It's important we keep the transfer function conversion separate
363 : : // from the color system decoding, so we have to partially undo our
364 : : // work here even though we will end up linearizing later on anyway)
365 : :
366 : 2 : GLSL(// PQ EOTF
367 : : "color.rgb = pow(max(color.rgb, 0.0), vec3(1.0/%f)); \n"
368 : : "color.rgb = max(color.rgb - vec3(%f), 0.0) \n"
369 : : " / (vec3(%f) - vec3(%f) * color.rgb); \n"
370 : : "color.rgb = pow(color.rgb, vec3(1.0/%f)); \n"
371 : : // LMS matrix
372 : : "color.rgb = mat3( 3.43661, -0.79133, -0.0259499, \n"
373 : : " -2.50645, 1.98360, -0.0989137, \n"
374 : : " 0.06984, -0.192271, 1.12486) * color.rgb; \n"
375 : : // PQ OETF
376 : : "color.rgb = pow(max(color.rgb, 0.0), vec3(%f)); \n"
377 : : "color.rgb = (vec3(%f) + vec3(%f) * color.rgb) \n"
378 : : " / (vec3(1.0) + vec3(%f) * color.rgb); \n"
379 : : "color.rgb = pow(color.rgb, vec3(%f)); \n",
380 : : PQ_M2, PQ_C1, PQ_C2, PQ_C3, PQ_M1,
381 : : PQ_M1, PQ_C1, PQ_C2, PQ_C3, PQ_M2);
382 : 2 : break;
383 : :
384 : : case PL_COLOR_SYSTEM_BT_2100_HLG:
385 : 2 : GLSL(// HLG OETF^-1
386 : : "color.rgb = mix(vec3(4.0) * color.rgb * color.rgb, \n"
387 : : " exp((color.rgb - vec3(%f)) * vec3(1.0/%f)) \n"
388 : : " + vec3(%f), \n"
389 : : " lessThan(vec3(0.5), color.rgb)); \n"
390 : : // LMS matrix
391 : : "color.rgb = mat3( 3.43661, -0.79133, -0.0259499, \n"
392 : : " -2.50645, 1.98360, -0.0989137, \n"
393 : : " 0.06984, -0.192271, 1.12486) * color.rgb; \n"
394 : : // HLG OETF
395 : : "color.rgb = mix(vec3(0.5) * sqrt(color.rgb), \n"
396 : : " vec3(%f) * log(color.rgb - vec3(%f)) + vec3(%f), \n"
397 : : " lessThan(vec3(1.0), color.rgb)); \n",
398 : : HLG_C, HLG_A, HLG_B,
399 : : HLG_A, HLG_B, HLG_C);
400 : 2 : break;
401 : :
402 : 2 : case PL_COLOR_SYSTEM_DOLBYVISION:;
403 : : #ifdef PL_HAVE_DOVI
404 : : // Dolby Vision always outputs BT.2020-referred HPE LMS, so hard-code
405 : : // the inverse LMS->RGB matrix corresponding to this color space.
406 : 2 : pl_matrix3x3 dovi_lms2rgb = {{
407 : : { 3.06441879, -2.16597676, 0.10155818},
408 : : {-0.65612108, 1.78554118, -0.12943749},
409 : : { 0.01736321, -0.04725154, 1.03004253},
410 : : }};
411 : :
412 : 2 : pl_matrix3x3_mul(&dovi_lms2rgb, &repr->dovi->linear);
413 : 2 : ident_t mat = sh_var(sh, (struct pl_shader_var) {
414 : 2 : .var = pl_var_mat3("lms2rgb"),
415 : 2 : .data = PL_TRANSPOSE_3X3(dovi_lms2rgb.m),
416 : : });
417 : :
418 : : // PQ EOTF
419 : 2 : GLSL("color.rgb = pow(max(color.rgb, 0.0), vec3(1.0/%f)); \n"
420 : : "color.rgb = max(color.rgb - vec3(%f), 0.0) \n"
421 : : " / (vec3(%f) - vec3(%f) * color.rgb); \n"
422 : : "color.rgb = pow(color.rgb, vec3(1.0/%f)); \n",
423 : : PQ_M2, PQ_C1, PQ_C2, PQ_C3, PQ_M1);
424 : : // LMS matrix
425 : 2 : GLSL("color.rgb = "$" * color.rgb; \n", mat);
426 : : // PQ OETF
427 : 2 : GLSL("color.rgb = pow(max(color.rgb, 0.0), vec3(%f)); \n"
428 : : "color.rgb = (vec3(%f) + vec3(%f) * color.rgb) \n"
429 : : " / (vec3(1.0) + vec3(%f) * color.rgb); \n"
430 : : "color.rgb = pow(color.rgb, vec3(%f)); \n",
431 : : PQ_M1, PQ_C1, PQ_C2, PQ_C3, PQ_M2);
432 : : break;
433 : : #else
434 : : SH_FAIL(sh, "libplacebo was compiled without support for dolbyvision reshaping");
435 : : return;
436 : : #endif
437 : :
438 : : case PL_COLOR_SYSTEM_UNKNOWN:
439 : : case PL_COLOR_SYSTEM_RGB:
440 : : case PL_COLOR_SYSTEM_XYZ:
441 : : case PL_COLOR_SYSTEM_BT_601:
442 : : case PL_COLOR_SYSTEM_BT_709:
443 : : case PL_COLOR_SYSTEM_SMPTE_240M:
444 : : case PL_COLOR_SYSTEM_BT_2020_NC:
445 : : case PL_COLOR_SYSTEM_YCGCO:
446 : : break; // no special post-processing needed
447 : :
448 : : case PL_COLOR_SYSTEM_COUNT:
449 : 0 : pl_unreachable();
450 : : }
451 : :
452 : : // Gamma adjustment. Doing this here (in non-linear light) is technically
453 : : // somewhat wrong, but this is just an aesthetic parameter and not really
454 : : // meant for colorimetric precision, so we don't care too much.
455 [ + + - + ]: 829 : if (params && params->gamma == 0) {
456 : : // Avoid division by zero
457 : 0 : GLSL("color.rgb = vec3(0.0); \n");
458 [ + + ]: 456 : } else if (params && params->gamma != 1) {
459 : 8 : ident_t gamma = sh_var(sh, (struct pl_shader_var) {
460 : 4 : .var = pl_var_float("gamma"),
461 : 4 : .data = &(float){ 1 / params->gamma },
462 : : });
463 : 4 : GLSL("color.rgb = pow(max(color.rgb, vec3(0.0)), vec3("$")); \n", gamma);
464 : : }
465 : :
466 : 829 : GLSL("}\n");
467 : : }
468 : :
469 : 1107 : void pl_shader_encode_color(pl_shader sh, const struct pl_color_repr *repr)
470 : : {
471 [ + - ]: 1107 : if (!sh_require(sh, PL_SHADER_SIG_COLOR, 0, 0))
472 : : return;
473 : :
474 : 1107 : sh_describe(sh, "color encoding");
475 : 1107 : GLSL("// pl_shader_encode_color \n"
476 : : "{ \n");
477 : :
478 [ + + + - : 1107 : switch (repr->sys) {
- + ]
479 : : case PL_COLOR_SYSTEM_BT_2020_C:
480 : : // Expand R'G'B' to RGB
481 : 2 : GLSL("vec3 lin = mix(color.rgb * vec3(1.0/4.5), \n"
482 : : " pow((color.rgb + vec3(0.0993))*vec3(1.0/1.0993), \n"
483 : : " vec3(1.0/0.45)), \n"
484 : : " lessThanEqual(vec3(0.08145), color.rgb)); \n");
485 : :
486 : : // Compute Yc from RGB and compress to R'Y'cB'
487 : 2 : GLSL("color.g = dot(vec3(0.2627, 0.6780, 0.0593), lin); \n"
488 : : "color.g = mix(color.g * 4.5, \n"
489 : : " 1.0993 * pow(color.g, 0.45) - 0.0993, \n"
490 : : " 0.0181 <= color.g); \n");
491 : :
492 : : // Compute C'bc and C'rc into color.br
493 : 2 : GLSL("color.br = color.br - color.gg; \n"
494 : : "color.br *= mix(vec2(1.0/1.5816, 1.0/0.9936), \n"
495 : : " vec2(1.0/1.9404, 1.0/1.7184), \n"
496 : : " lessThanEqual(color.br, vec2(0.0))); \n");
497 : : break;
498 : :
499 : : case PL_COLOR_SYSTEM_BT_2100_PQ:;
500 : 2 : GLSL("color.rgb = pow(max(color.rgb, 0.0), vec3(1.0/%f)); \n"
501 : : "color.rgb = max(color.rgb - vec3(%f), 0.0) \n"
502 : : " / (vec3(%f) - vec3(%f) * color.rgb); \n"
503 : : "color.rgb = pow(color.rgb, vec3(1.0/%f)); \n"
504 : : "color.rgb = mat3(0.412109, 0.166748, 0.024170, \n"
505 : : " 0.523925, 0.720459, 0.075440, \n"
506 : : " 0.063965, 0.112793, 0.900394) * color.rgb; \n"
507 : : "color.rgb = pow(color.rgb, vec3(%f)); \n"
508 : : "color.rgb = (vec3(%f) + vec3(%f) * color.rgb) \n"
509 : : " / (vec3(1.0) + vec3(%f) * color.rgb); \n"
510 : : "color.rgb = pow(color.rgb, vec3(%f)); \n",
511 : : PQ_M2, PQ_C1, PQ_C2, PQ_C3, PQ_M1,
512 : : PQ_M1, PQ_C1, PQ_C2, PQ_C3, PQ_M2);
513 : 2 : break;
514 : :
515 : : case PL_COLOR_SYSTEM_BT_2100_HLG:
516 : 2 : GLSL("color.rgb = mix(vec3(4.0) * color.rgb * color.rgb, \n"
517 : : " exp((color.rgb - vec3(%f)) * vec3(1.0/%f)) \n"
518 : : " + vec3(%f), \n"
519 : : " lessThan(vec3(0.5), color.rgb)); \n"
520 : : "color.rgb = mat3(0.412109, 0.166748, 0.024170, \n"
521 : : " 0.523925, 0.720459, 0.075440, \n"
522 : : " 0.063965, 0.112793, 0.900394) * color.rgb; \n"
523 : : "color.rgb = mix(vec3(0.5) * sqrt(color.rgb), \n"
524 : : " vec3(%f) * log(color.rgb - vec3(%f)) + vec3(%f), \n"
525 : : " lessThan(vec3(1.0), color.rgb)); \n",
526 : : HLG_C, HLG_A, HLG_B,
527 : : HLG_A, HLG_B, HLG_C);
528 : 2 : break;
529 : :
530 : 0 : case PL_COLOR_SYSTEM_DOLBYVISION:
531 : 0 : SH_FAIL(sh, "Cannot un-apply dolbyvision yet (no inverse reshaping)!");
532 : 0 : return;
533 : :
534 : : case PL_COLOR_SYSTEM_UNKNOWN:
535 : : case PL_COLOR_SYSTEM_RGB:
536 : : case PL_COLOR_SYSTEM_XYZ:
537 : : case PL_COLOR_SYSTEM_BT_601:
538 : : case PL_COLOR_SYSTEM_BT_709:
539 : : case PL_COLOR_SYSTEM_SMPTE_240M:
540 : : case PL_COLOR_SYSTEM_BT_2020_NC:
541 : : case PL_COLOR_SYSTEM_YCGCO:
542 : : break; // no special pre-processing needed
543 : :
544 : : case PL_COLOR_SYSTEM_COUNT:
545 : 0 : pl_unreachable();
546 : : }
547 : :
548 : : // Since this is a relatively rare operation, bypass it as much as possible
549 : : bool skip = true;
550 : 1107 : skip &= PL_DEF(repr->sys, PL_COLOR_SYSTEM_RGB) == PL_COLOR_SYSTEM_RGB;
551 : 1107 : skip &= PL_DEF(repr->levels, PL_COLOR_LEVELS_FULL) == PL_COLOR_LEVELS_FULL;
552 [ + + - + : 1107 : skip &= !repr->bits.sample_depth || !repr->bits.color_depth ||
+ - ]
553 : : repr->bits.sample_depth == repr->bits.color_depth;
554 : 1107 : skip &= !repr->bits.bit_shift;
555 : :
556 [ + + ]: 1107 : if (!skip) {
557 : 23 : struct pl_color_repr copy = *repr;
558 : : ident_t xyzscale = NULL_IDENT;
559 [ + + ]: 23 : if (repr->sys == PL_COLOR_SYSTEM_XYZ)
560 : 2 : xyzscale = SH_FLOAT(1.0 / pl_color_repr_normalize(©));
561 : :
562 : 23 : pl_transform3x3 tr = pl_color_repr_decode(©, NULL);
563 : 23 : pl_transform3x3_invert(&tr);
564 : :
565 : 23 : ident_t cmat = sh_var(sh, (struct pl_shader_var) {
566 : 23 : .var = pl_var_mat3("cmat"),
567 : 23 : .data = PL_TRANSPOSE_3X3(tr.mat.m),
568 : : });
569 : :
570 : 23 : ident_t cmat_c = sh_var(sh, (struct pl_shader_var) {
571 : 23 : .var = pl_var_vec3("cmat_c"),
572 : : .data = tr.c,
573 : : });
574 : :
575 : 23 : GLSL("color.rgb = "$" * color.rgb + "$"; \n", cmat, cmat_c);
576 : :
577 [ + + ]: 23 : if (repr->sys == PL_COLOR_SYSTEM_XYZ) {
578 : 2 : pl_shader_delinearize(sh, &(struct pl_color_space) {
579 : : .transfer = PL_COLOR_TRC_ST428,
580 : : });
581 : 2 : GLSL("color.rgb *= vec3("$"); \n", xyzscale);
582 : : }
583 : : }
584 : :
585 [ - + ]: 1107 : if (repr->alpha == PL_ALPHA_PREMULTIPLIED)
586 : 0 : GLSL("color.rgb *= vec3(color.a); \n");
587 : :
588 : 1107 : GLSL("}\n");
589 : : }
590 : :
591 : 18 : static ident_t sh_luma_coeffs(pl_shader sh, const struct pl_color_space *csp)
592 : : {
593 : : pl_matrix3x3 rgb2xyz;
594 : 18 : rgb2xyz = pl_get_rgb2xyz_matrix(pl_raw_primaries_get(csp->primaries));
595 : :
596 : : // FIXME: Cannot use `const vec3` due to glslang bug #2025
597 : 18 : ident_t coeffs = sh_fresh(sh, "luma_coeffs");
598 : 18 : GLSLH("#define "$" vec3("$", "$", "$") \n", coeffs,
599 : : SH_FLOAT(rgb2xyz.m[1][0]), // RGB->Y vector
600 : : SH_FLOAT(rgb2xyz.m[1][1]),
601 : : SH_FLOAT(rgb2xyz.m[1][2]));
602 : 18 : return coeffs;
603 : : }
604 : :
605 : 322 : void pl_shader_linearize(pl_shader sh, const struct pl_color_space *csp)
606 : : {
607 [ + - ]: 322 : if (!sh_require(sh, PL_SHADER_SIG_COLOR, 0, 0))
608 : 64 : return;
609 : :
610 [ + + ]: 322 : if (csp->transfer == PL_COLOR_TRC_LINEAR)
611 : : return;
612 : :
613 : : float csp_min, csp_max;
614 : 320 : pl_color_space_nominal_luma_ex(pl_nominal_luma_params(
615 : : .color = csp,
616 : : .metadata = PL_HDR_METADATA_HDR10,
617 : : .scaling = PL_HDR_NORM,
618 : : .out_min = &csp_min,
619 : : .out_max = &csp_max,
620 : : ));
621 : :
622 : : // Note that this clamp may technically violate the definition of
623 : : // ITU-R BT.2100, which allows for sub-blacks and super-whites to be
624 : : // displayed on the display where such would be possible. That said, the
625 : : // problem is that not all gamma curves are well-defined on the values
626 : : // outside this range, so we ignore it and just clamp anyway for sanity.
627 : 320 : GLSL("// pl_shader_linearize \n"
628 : : "color.rgb = max(color.rgb, 0.0); \n");
629 : :
630 [ + + + + : 320 : switch (csp->transfer) {
+ + + + +
+ + + + +
+ - ]
631 : : case PL_COLOR_TRC_SRGB:
632 : 236 : GLSL("color.rgb = mix(color.rgb * vec3(1.0/12.92), \n"
633 : : " pow((color.rgb + vec3(0.055))/vec3(1.055), \n"
634 : : " vec3(2.4)), \n"
635 : : " lessThan(vec3(0.04045), color.rgb)); \n");
636 : 236 : goto scale_out;
637 : 18 : case PL_COLOR_TRC_BT_1886: {
638 : 18 : const float lb = powf(csp_min, 1/2.4f);
639 : 18 : const float lw = powf(csp_max, 1/2.4f);
640 : 18 : const float a = powf(lw - lb, 2.4f);
641 : 18 : const float b = lb / (lw - lb);
642 : 18 : GLSL("color.rgb = "$" * pow(color.rgb + vec3("$"), vec3(2.4)); \n",
643 : : SH_FLOAT(a), SH_FLOAT(b));
644 : : return;
645 : : }
646 : : case PL_COLOR_TRC_GAMMA18:
647 : 2 : GLSL("color.rgb = pow(color.rgb, vec3(1.8));\n");
648 : 2 : goto scale_out;
649 : : case PL_COLOR_TRC_GAMMA20:
650 : 2 : GLSL("color.rgb = pow(color.rgb, vec3(2.0));\n");
651 : 2 : goto scale_out;
652 : : case PL_COLOR_TRC_UNKNOWN:
653 : : case PL_COLOR_TRC_GAMMA22:
654 : 6 : GLSL("color.rgb = pow(color.rgb, vec3(2.2));\n");
655 : 6 : goto scale_out;
656 : : case PL_COLOR_TRC_GAMMA24:
657 : 2 : GLSL("color.rgb = pow(color.rgb, vec3(2.4));\n");
658 : 2 : goto scale_out;
659 : : case PL_COLOR_TRC_GAMMA26:
660 : 2 : GLSL("color.rgb = pow(color.rgb, vec3(2.6));\n");
661 : 2 : goto scale_out;
662 : : case PL_COLOR_TRC_GAMMA28:
663 : 2 : GLSL("color.rgb = pow(color.rgb, vec3(2.8));\n");
664 : 2 : goto scale_out;
665 : : case PL_COLOR_TRC_PRO_PHOTO:
666 : 2 : GLSL("color.rgb = mix(color.rgb * vec3(1.0/16.0), \n"
667 : : " pow(color.rgb, vec3(1.8)), \n"
668 : : " lessThan(vec3(0.03125), color.rgb)); \n");
669 : 2 : goto scale_out;
670 : : case PL_COLOR_TRC_ST428:
671 : 4 : GLSL("color.rgb = vec3(52.37/48.0) * pow(color.rgb, vec3(2.6));\n");
672 : 4 : goto scale_out;
673 : : case PL_COLOR_TRC_PQ:
674 : 36 : GLSL("color.rgb = pow(color.rgb, vec3(1.0/%f)); \n"
675 : : "color.rgb = max(color.rgb - vec3(%f), 0.0) \n"
676 : : " / (vec3(%f) - vec3(%f) * color.rgb); \n"
677 : : "color.rgb = pow(color.rgb, vec3(1.0/%f)); \n"
678 : : // PQ's output range is 0-10000, but we need it to be relative to
679 : : // to PL_COLOR_SDR_WHITE instead, so rescale
680 : : "color.rgb *= vec3(%f); \n",
681 : : PQ_M2, PQ_C1, PQ_C2, PQ_C3, PQ_M1, 10000.0 / PL_COLOR_SDR_WHITE);
682 : 36 : return;
683 : 2 : case PL_COLOR_TRC_HLG: {
684 : 2 : const float y = fmaxf(1.2f + 0.42f * log10f(csp_max / HLG_REF), 1);
685 : 2 : const float b = sqrtf(3 * powf(csp_min / csp_max, 1 / y));
686 : : // OETF^-1
687 : 2 : GLSL("color.rgb = "$" * color.rgb + vec3("$"); \n"
688 : : "color.rgb = mix(vec3(4.0) * color.rgb * color.rgb, \n"
689 : : " exp((color.rgb - vec3(%f)) * vec3(1.0/%f))\n"
690 : : " + vec3(%f), \n"
691 : : " lessThan(vec3(0.5), color.rgb)); \n",
692 : : SH_FLOAT(1 - b), SH_FLOAT(b),
693 : : HLG_C, HLG_A, HLG_B);
694 : : // OOTF
695 : 2 : GLSL("color.rgb *= 1.0 / 12.0; \n"
696 : : "color.rgb *= "$" * pow(max(dot("$", color.rgb), 0.0), "$"); \n",
697 : : SH_FLOAT(csp_max), sh_luma_coeffs(sh, csp), SH_FLOAT(y - 1));
698 : : return;
699 : : }
700 : : case PL_COLOR_TRC_V_LOG:
701 : 2 : GLSL("color.rgb = mix((color.rgb - vec3(0.125)) * vec3(1.0/5.6), \n"
702 : : " pow(vec3(10.0), (color.rgb - vec3(%f)) * vec3(1.0/%f)) \n"
703 : : " - vec3(%f), \n"
704 : : " lessThanEqual(vec3(0.181), color.rgb)); \n",
705 : : VLOG_D, VLOG_C, VLOG_B);
706 : 2 : return;
707 : : case PL_COLOR_TRC_S_LOG1:
708 : 2 : GLSL("color.rgb = pow(vec3(10.0), (color.rgb - vec3(%f)) * vec3(1.0/%f)) \n"
709 : : " - vec3(%f); \n",
710 : : SLOG_C, SLOG_A, SLOG_B);
711 : 2 : return;
712 : : case PL_COLOR_TRC_S_LOG2:
713 : 2 : GLSL("color.rgb = mix((color.rgb - vec3(%f)) * vec3(1.0/%f), \n"
714 : : " (pow(vec3(10.0), (color.rgb - vec3(%f)) * vec3(1.0/%f)) \n"
715 : : " - vec3(%f)) * vec3(1.0/%f), \n"
716 : : " lessThanEqual(vec3(%f), color.rgb)); \n",
717 : : SLOG_Q, SLOG_P, SLOG_C, SLOG_A, SLOG_B, SLOG_K2, SLOG_Q);
718 : 2 : return;
719 : : case PL_COLOR_TRC_LINEAR:
720 : : case PL_COLOR_TRC_COUNT:
721 : : break;
722 : : }
723 : :
724 : 0 : pl_unreachable();
725 : :
726 : 258 : scale_out:
727 [ + - + - ]: 258 : if (csp_max != 1 || csp_min != 0) {
728 : 258 : GLSL("color.rgb = "$" * color.rgb + vec3("$"); \n",
729 : : SH_FLOAT(csp_max - csp_min), SH_FLOAT(csp_min));
730 : : }
731 : : }
732 : :
733 : 324 : void pl_shader_delinearize(pl_shader sh, const struct pl_color_space *csp)
734 : : {
735 [ + - ]: 324 : if (!sh_require(sh, PL_SHADER_SIG_COLOR, 0, 0))
736 : : return;
737 : :
738 [ + + ]: 324 : if (csp->transfer == PL_COLOR_TRC_LINEAR)
739 : : return;
740 : :
741 : : float csp_min, csp_max;
742 : 306 : pl_color_space_nominal_luma_ex(pl_nominal_luma_params(
743 : : .color = csp,
744 : : .metadata = PL_HDR_METADATA_HDR10,
745 : : .scaling = PL_HDR_NORM,
746 : : .out_min = &csp_min,
747 : : .out_max = &csp_max,
748 : : ));
749 : :
750 : 306 : GLSL("// pl_shader_delinearize \n");
751 [ + + ]: 306 : if (pl_color_space_is_black_scaled(csp) &&
752 [ + + ]: 296 : csp->transfer != PL_COLOR_TRC_HLG &&
753 [ + + + - ]: 294 : (csp_max != 1 || csp_min != 0))
754 : : {
755 : 294 : GLSL("color.rgb = "$" * color.rgb + vec3("$"); \n",
756 : : SH_FLOAT(1 / (csp_max - csp_min)),
757 : : SH_FLOAT(-csp_min / (csp_max - csp_min)));
758 : : }
759 : :
760 : 306 : GLSL("color.rgb = max(color.rgb, 0.0); \n");
761 : :
762 [ + + + + : 306 : switch (csp->transfer) {
+ + + + +
+ + + + +
+ - ]
763 : : case PL_COLOR_TRC_SRGB:
764 : 274 : GLSL("color.rgb = mix(color.rgb * vec3(12.92), \n"
765 : : " vec3(1.055) * pow(color.rgb, vec3(1.0/2.4)) \n"
766 : : " - vec3(0.055), \n"
767 : : " lessThanEqual(vec3(0.0031308), color.rgb)); \n");
768 : 274 : return;
769 : 2 : case PL_COLOR_TRC_BT_1886: {
770 : 2 : const float lb = powf(csp_min, 1/2.4f);
771 : 2 : const float lw = powf(csp_max, 1/2.4f);
772 : 2 : const float a = powf(lw - lb, 2.4f);
773 : 2 : const float b = lb / (lw - lb);
774 : 2 : GLSL("color.rgb = pow("$" * color.rgb, vec3(1.0/2.4)) - vec3("$"); \n",
775 : : SH_FLOAT(1.0 / a), SH_FLOAT(b));
776 : : return;
777 : : }
778 : : case PL_COLOR_TRC_GAMMA18:
779 : 2 : GLSL("color.rgb = pow(color.rgb, vec3(1.0/1.8));\n");
780 : 2 : return;
781 : : case PL_COLOR_TRC_GAMMA20:
782 : 2 : GLSL("color.rgb = pow(color.rgb, vec3(1.0/2.0));\n");
783 : 2 : return;
784 : : case PL_COLOR_TRC_UNKNOWN:
785 : : case PL_COLOR_TRC_GAMMA22:
786 : 4 : GLSL("color.rgb = pow(color.rgb, vec3(1.0/2.2));\n");
787 : 4 : return;
788 : : case PL_COLOR_TRC_GAMMA24:
789 : 2 : GLSL("color.rgb = pow(color.rgb, vec3(1.0/2.4));\n");
790 : 2 : return;
791 : : case PL_COLOR_TRC_GAMMA26:
792 : 2 : GLSL("color.rgb = pow(color.rgb, vec3(1.0/2.6));\n");
793 : 2 : return;
794 : : case PL_COLOR_TRC_GAMMA28:
795 : 2 : GLSL("color.rgb = pow(color.rgb, vec3(1.0/2.8));\n");
796 : 2 : return;
797 : : case PL_COLOR_TRC_ST428:
798 : 4 : GLSL("color.rgb = pow(color.rgb * vec3(48.0/52.37), vec3(1.0/2.6));\n");
799 : 4 : return;
800 : : case PL_COLOR_TRC_PRO_PHOTO:
801 : 2 : GLSL("color.rgb = mix(color.rgb * vec3(16.0), \n"
802 : : " pow(color.rgb, vec3(1.0/1.8)), \n"
803 : : " lessThanEqual(vec3(0.001953), color.rgb)); \n");
804 : 2 : return;
805 : : case PL_COLOR_TRC_PQ:
806 : 2 : GLSL("color.rgb *= vec3(1.0/%f); \n"
807 : : "color.rgb = pow(color.rgb, vec3(%f)); \n"
808 : : "color.rgb = (vec3(%f) + vec3(%f) * color.rgb) \n"
809 : : " / (vec3(1.0) + vec3(%f) * color.rgb); \n"
810 : : "color.rgb = pow(color.rgb, vec3(%f)); \n",
811 : : 10000 / PL_COLOR_SDR_WHITE, PQ_M1, PQ_C1, PQ_C2, PQ_C3, PQ_M2);
812 : 2 : return;
813 : 2 : case PL_COLOR_TRC_HLG: {
814 : 2 : const float y = fmaxf(1.2f + 0.42f * log10f(csp_max / HLG_REF), 1);
815 : 2 : const float b = sqrtf(3 * powf(csp_min / csp_max, 1 / y));
816 : : // OOTF^-1
817 : 2 : GLSL("color.rgb *= 1.0 / "$"; \n"
818 : : "color.rgb *= 12.0 * max(1e-6, pow(dot("$", color.rgb), "$")); \n",
819 : : SH_FLOAT(csp_max), sh_luma_coeffs(sh, csp), SH_FLOAT((1 - y) / y));
820 : : // OETF
821 : 2 : GLSL("color.rgb = mix(vec3(0.5) * sqrt(color.rgb), \n"
822 : : " vec3(%f) * log(color.rgb - vec3(%f)) + vec3(%f), \n"
823 : : " lessThan(vec3(1.0), color.rgb)); \n"
824 : : "color.rgb = "$" * color.rgb + vec3("$"); \n",
825 : : HLG_A, HLG_B, HLG_C,
826 : : SH_FLOAT(1 / (1 - b)), SH_FLOAT(-b / (1 - b)));
827 : : return;
828 : : }
829 : : case PL_COLOR_TRC_V_LOG:
830 : 2 : GLSL("color.rgb = mix(vec3(5.6) * color.rgb + vec3(0.125), \n"
831 : : " vec3(%f) * log(color.rgb + vec3(%f)) \n"
832 : : " + vec3(%f), \n"
833 : : " lessThanEqual(vec3(0.01), color.rgb)); \n",
834 : : VLOG_C / M_LN10, VLOG_B, VLOG_D);
835 : 2 : return;
836 : : case PL_COLOR_TRC_S_LOG1:
837 : 2 : GLSL("color.rgb = vec3(%f) * log(color.rgb + vec3(%f)) + vec3(%f);\n",
838 : : SLOG_A / M_LN10, SLOG_B, SLOG_C);
839 : 2 : return;
840 : : case PL_COLOR_TRC_S_LOG2:
841 : 2 : GLSL("color.rgb = mix(vec3(%f) * color.rgb + vec3(%f), \n"
842 : : " vec3(%f) * log(vec3(%f) * color.rgb + vec3(%f)) \n"
843 : : " + vec3(%f), \n"
844 : : " lessThanEqual(vec3(0.0), color.rgb)); \n",
845 : : SLOG_P, SLOG_Q, SLOG_A / M_LN10, SLOG_K2, SLOG_B, SLOG_C);
846 : 2 : return;
847 : : case PL_COLOR_TRC_LINEAR:
848 : : case PL_COLOR_TRC_COUNT:
849 : : break;
850 : : }
851 : :
852 : 0 : pl_unreachable();
853 : : }
854 : :
855 : : const struct pl_sigmoid_params pl_sigmoid_default_params = { PL_SIGMOID_DEFAULTS };
856 : :
857 : 96 : void pl_shader_sigmoidize(pl_shader sh, const struct pl_sigmoid_params *params)
858 : : {
859 [ + - ]: 96 : if (!sh_require(sh, PL_SHADER_SIG_COLOR, 0, 0))
860 : : return;
861 : :
862 [ - + ]: 96 : params = PL_DEF(params, &pl_sigmoid_default_params);
863 [ + - ]: 96 : float center = PL_DEF(params->center, pl_sigmoid_default_params.center);
864 [ + - ]: 96 : float slope = PL_DEF(params->slope, pl_sigmoid_default_params.slope);
865 : :
866 : : // This function needs to go through (0,0) and (1,1), so we compute the
867 : : // values at 1 and 0, and then scale/shift them, respectively.
868 : 96 : float offset = 1.0 / (1 + expf(slope * center));
869 : 96 : float scale = 1.0 / (1 + expf(slope * (center - 1))) - offset;
870 : :
871 : 96 : GLSL("// pl_shader_sigmoidize \n"
872 : : "color.rgb = clamp(color.rgb, 0.0, 1.0); \n"
873 : : "color.rgb = vec3("$") - vec3("$") * \n"
874 : : " log(vec3(1.0) / (color.rgb * vec3("$") + vec3("$")) \n"
875 : : " - vec3(1.0)); \n",
876 : : SH_FLOAT(center), SH_FLOAT(1.0 / slope),
877 : : SH_FLOAT(scale), SH_FLOAT(offset));
878 : : }
879 : :
880 : 96 : void pl_shader_unsigmoidize(pl_shader sh, const struct pl_sigmoid_params *params)
881 : : {
882 [ + - ]: 96 : if (!sh_require(sh, PL_SHADER_SIG_COLOR, 0, 0))
883 : : return;
884 : :
885 : : // See: pl_shader_sigmoidize
886 [ - + ]: 96 : params = PL_DEF(params, &pl_sigmoid_default_params);
887 [ + - ]: 96 : float center = PL_DEF(params->center, pl_sigmoid_default_params.center);
888 [ + - ]: 96 : float slope = PL_DEF(params->slope, pl_sigmoid_default_params.slope);
889 : 96 : float offset = 1.0 / (1 + expf(slope * center));
890 : 96 : float scale = 1.0 / (1 + expf(slope * (center - 1))) - offset;
891 : :
892 : 96 : GLSL("// pl_shader_unsigmoidize \n"
893 : : "color.rgb = clamp(color.rgb, 0.0, 1.0); \n"
894 : : "color.rgb = vec3("$") / \n"
895 : : " (vec3(1.0) + exp(vec3("$") * (vec3("$") - color.rgb))) \n"
896 : : " - vec3("$"); \n",
897 : : SH_FLOAT(1.0 / scale),
898 : : SH_FLOAT(slope), SH_FLOAT(center),
899 : : SH_FLOAT(offset / scale));
900 : : }
901 : :
902 : : const struct pl_peak_detect_params pl_peak_detect_default_params = { PL_PEAK_DETECT_DEFAULTS };
903 : : const struct pl_peak_detect_params pl_peak_detect_high_quality_params = { PL_PEAK_DETECT_HQ_DEFAULTS };
904 : :
905 : : static bool peak_detect_params_eq(const struct pl_peak_detect_params *a,
906 : : const struct pl_peak_detect_params *b)
907 : : {
908 : 26 : return a->smoothing_period == b->smoothing_period &&
909 [ + - ]: 12 : a->scene_threshold_low == b->scene_threshold_low &&
910 [ + + + - ]: 26 : a->scene_threshold_high == b->scene_threshold_high &&
911 [ - + ]: 12 : a->percentile == b->percentile;
912 : : // don't compare `allow_delayed` because it doesn't change measurement
913 : : }
914 : :
915 : : enum {
916 : : // Split the peak buffer into several independent slices to reduce pressure
917 : : // on global atomics
918 : : SLICES = 12,
919 : :
920 : : // How many bits to use for storing PQ data. Be careful when setting this
921 : : // too high, as it may overflow `unsigned int` on large video sources.
922 : : //
923 : : // The value chosen is enough to guarantee no overflow for an 8K x 4K frame
924 : : // consisting entirely of 100% 10k nits PQ values, with 16x16 workgroups.
925 : : PQ_BITS = 14,
926 : : PQ_MAX = (1 << PQ_BITS) - 1,
927 : :
928 : : // How many bits to use for the histogram. We bias the histogram down
929 : : // by half the PQ range (~90 nits), effectively clumping the SDR part
930 : : // of the image into a single histogram bin.
931 : : HIST_BITS = 7,
932 : : HIST_BIAS = 1 << (HIST_BITS - 1),
933 : : HIST_BINS = (1 << HIST_BITS) - HIST_BIAS,
934 : :
935 : : // Convert from histogram bin to (starting) PQ value
936 : : #define HIST_PQ(bin) (((bin) + HIST_BIAS) << (PQ_BITS - HIST_BITS))
937 : : };
938 : :
939 : :
940 : : pl_static_assert(PQ_BITS >= HIST_BITS);
941 : :
942 : : struct peak_buf_data {
943 : : unsigned frame_wg_count[SLICES]; // number of work groups processed
944 : : unsigned frame_wg_active[SLICES];// number of active (nonzero) work groups
945 : : unsigned frame_sum_pq[SLICES]; // sum of PQ Y values over all WGs (PQ_BITS)
946 : : unsigned frame_max_pq[SLICES]; // maximum PQ Y value among these WGs (PQ_BITS)
947 : : unsigned frame_hist[SLICES][HIST_BINS]; // always allocated, conditionally used
948 : : };
949 : :
950 : : static const struct pl_buffer_var peak_buf_vars[] = {
951 : : #define VAR(field) { \
952 : : .var = { \
953 : : .name = #field, \
954 : : .type = PL_VAR_UINT, \
955 : : .dim_v = 1, \
956 : : .dim_m = 1, \
957 : : .dim_a = sizeof(((struct peak_buf_data *) NULL)->field) / \
958 : : sizeof(unsigned), \
959 : : }, \
960 : : .layout = { \
961 : : .offset = offsetof(struct peak_buf_data, field), \
962 : : .size = sizeof(((struct peak_buf_data *) NULL)->field), \
963 : : .stride = sizeof(unsigned), \
964 : : }, \
965 : : }
966 : : VAR(frame_wg_count),
967 : : VAR(frame_wg_active),
968 : : VAR(frame_sum_pq),
969 : : VAR(frame_max_pq),
970 : : VAR(frame_hist),
971 : : #undef VAR
972 : : };
973 : :
974 : : struct sh_color_map_obj {
975 : : // Tone map state
976 : : struct {
977 : : struct pl_tone_map_params params;
978 : : pl_shader_obj lut;
979 : : } tone;
980 : :
981 : : // Gamut map state
982 : : struct {
983 : : pl_shader_obj lut;
984 : : } gamut;
985 : :
986 : : // Peak detection state
987 : : struct {
988 : : struct pl_peak_detect_params params; // currently active parameters
989 : : pl_buf buf; // pending peak detection buffer
990 : : pl_buf readback; // readback buffer (fallback)
991 : : float avg_pq; // current (smoothed) values
992 : : float max_pq;
993 : : } peak;
994 : : };
995 : :
996 : : // Excluding size, since this is checked by sh_lut
997 : 30 : static uint64_t gamut_map_signature(const struct pl_gamut_map_params *par)
998 : : {
999 : : uint64_t sig = CACHE_KEY_GAMUT_LUT;
1000 : 30 : pl_hash_merge(&sig, pl_str0_hash(par->function->name));
1001 : 30 : pl_hash_merge(&sig, pl_var_hash(par->input_gamut));
1002 : 30 : pl_hash_merge(&sig, pl_var_hash(par->output_gamut));
1003 : 30 : pl_hash_merge(&sig, pl_var_hash(par->min_luma));
1004 : 30 : pl_hash_merge(&sig, pl_var_hash(par->max_luma));
1005 : 30 : pl_hash_merge(&sig, pl_var_hash(par->constants));
1006 : 30 : return sig;
1007 : : }
1008 : :
1009 : 11 : static void sh_color_map_uninit(pl_gpu gpu, void *ptr)
1010 : : {
1011 : : struct sh_color_map_obj *obj = ptr;
1012 : 11 : pl_shader_obj_destroy(&obj->tone.lut);
1013 : 11 : pl_shader_obj_destroy(&obj->gamut.lut);
1014 : 11 : pl_buf_destroy(gpu, &obj->peak.buf);
1015 : 11 : pl_buf_destroy(gpu, &obj->peak.readback);
1016 : : memset(obj, 0, sizeof(*obj));
1017 : 11 : }
1018 : :
1019 : : static inline float iir_coeff(float rate)
1020 : : {
1021 : 14 : if (!rate)
1022 : : return 1.0f;
1023 : 12 : return 1.0f - expf(-1.0f / rate);
1024 : : }
1025 : :
1026 : 14 : static float measure_peak(const struct peak_buf_data *data, float percentile)
1027 : : {
1028 : 14 : unsigned frame_max_pq = data->frame_max_pq[0];
1029 [ + + ]: 168 : for (int k = 1; k < SLICES; k++)
1030 : 154 : frame_max_pq = PL_MAX(frame_max_pq, data->frame_max_pq[k]);
1031 : 14 : const float frame_max = (float) frame_max_pq / PQ_MAX;
1032 [ + + - + ]: 14 : if (percentile <= 0 || percentile >= 100)
1033 : : return frame_max;
1034 : : unsigned total_pixels = 0;
1035 [ # # ]: 0 : for (int k = 0; k < SLICES; k++) {
1036 [ # # ]: 0 : for (int i = 0; i < HIST_BINS; i++)
1037 : 0 : total_pixels += data->frame_hist[k][i];
1038 : : }
1039 [ # # ]: 0 : if (!total_pixels) // no histogram data available?
1040 : : return frame_max;
1041 : :
1042 : 0 : const unsigned target_pixel = ceilf(percentile / 100.0f * total_pixels);
1043 [ # # ]: 0 : if (target_pixel >= total_pixels)
1044 : : return frame_max;
1045 : :
1046 : : unsigned sum = 0;
1047 [ # # ]: 0 : for (int i = 0; i < HIST_BINS; i++) {
1048 : : unsigned next = sum;
1049 [ # # ]: 0 : for (int k = 0; k < SLICES; k++)
1050 : 0 : next += data->frame_hist[k][i];
1051 [ # # ]: 0 : if (next < target_pixel) {
1052 : : sum = next;
1053 : : continue;
1054 : : }
1055 : :
1056 : : // Upper and lower frequency boundaries of the matching histogram bin
1057 : : const unsigned count_low = sum; // last pixel of previous bin
1058 : 0 : const unsigned count_high = next + 1; // first pixel of next bin
1059 [ # # ]: 0 : pl_assert(count_low < target_pixel && target_pixel < count_high);
1060 : :
1061 : : // PQ luminance associated with count_low/high respectively
1062 : 0 : const float pq_low = (float) HIST_PQ(i) / PQ_MAX;
1063 : 0 : float pq_high = (float) HIST_PQ(i + 1) / PQ_MAX;
1064 [ # # ]: 0 : if (count_high > total_pixels) // special case for last histogram bin
1065 : : pq_high = frame_max;
1066 : :
1067 : : // Position of `target_pixel` inside this bin, assumes pixels are
1068 : : // equidistributed inside a histogram bin
1069 : 0 : const float ratio = (float) (target_pixel - count_low) /
1070 : 0 : (count_high - count_low);
1071 : 0 : return PL_MIX(pq_low, pq_high, ratio);
1072 : : }
1073 : :
1074 : 0 : pl_unreachable();
1075 : : }
1076 : :
1077 : : // if `force` is true, ensures the buffer is read, even if `allow_delayed`
1078 : 794 : static void update_peak_buf(pl_gpu gpu, struct sh_color_map_obj *obj, bool force)
1079 : : {
1080 : : const struct pl_peak_detect_params *params = &obj->peak.params;
1081 [ + + ]: 794 : if (!obj->peak.buf)
1082 : 780 : return;
1083 : :
1084 [ + + + + : 16 : if (!force && params->allow_delayed && pl_buf_poll(gpu, obj->peak.buf, 0))
+ - ]
1085 : : return; // buffer not ready yet
1086 : :
1087 : : bool ok;
1088 : 16 : struct peak_buf_data data = {0};
1089 [ - + ]: 16 : if (obj->peak.readback) {
1090 : 0 : pl_buf_copy(gpu, obj->peak.readback, 0, obj->peak.buf, 0, sizeof(data));
1091 : 0 : ok = pl_buf_read(gpu, obj->peak.readback, 0, &data, sizeof(data));
1092 : : } else {
1093 : 16 : ok = pl_buf_read(gpu, obj->peak.buf, 0, &data, sizeof(data));
1094 : : }
1095 [ + - + + ]: 16 : if (ok && data.frame_wg_count[0] > 0) {
1096 : : // Peak detection completed successfully
1097 : 14 : pl_buf_destroy(gpu, &obj->peak.buf);
1098 : : } else {
1099 : : // No data read? Possibly this peak obj has not been executed yet
1100 : : if (!ok) {
1101 : 0 : PL_ERR(gpu, "Failed reading peak detection buffer!");
1102 [ + - ]: 2 : } else if (params->allow_delayed) {
1103 : 2 : PL_TRACE(gpu, "Peak detection buffer not yet ready, ignoring..");
1104 : : } else {
1105 : 0 : PL_WARN(gpu, "Peak detection usage error: attempted detecting peak "
1106 : : "and using detected peak in the same shader program, "
1107 : : "but `params->allow_delayed` is false! Ignoring, but "
1108 : : "expect incorrect output.");
1109 : : }
1110 [ - + ]: 2 : if (force || !ok)
1111 : 0 : pl_buf_destroy(gpu, &obj->peak.buf);
1112 : 2 : return;
1113 : : }
1114 : :
1115 : : uint64_t frame_sum_pq = 0u, frame_wg_count = 0u, frame_wg_active = 0u;
1116 [ + + ]: 182 : for (int k = 0; k < SLICES; k++) {
1117 : 168 : frame_sum_pq += data.frame_sum_pq[k];
1118 : 168 : frame_wg_count += data.frame_wg_count[k];
1119 : 168 : frame_wg_active += data.frame_wg_active[k];
1120 : : }
1121 : : float avg_pq, max_pq;
1122 [ + - ]: 14 : if (frame_wg_active) {
1123 : 14 : avg_pq = (float) frame_sum_pq / (frame_wg_active * PQ_MAX);
1124 : 14 : max_pq = measure_peak(&data, params->percentile);
1125 : : } else {
1126 : : // Solid black frame
1127 : : avg_pq = max_pq = PL_COLOR_HDR_BLACK;
1128 : : }
1129 : :
1130 [ + + ]: 14 : if (!obj->peak.avg_pq) {
1131 : : // Set the initial value accordingly if it contains no data
1132 : 4 : obj->peak.avg_pq = avg_pq;
1133 : 4 : obj->peak.max_pq = max_pq;
1134 : : } else {
1135 : : // Ignore small deviations from existing peak (rounding error)
1136 : : static const float epsilon = 1.0f / PQ_MAX;
1137 [ + - ]: 10 : if (fabsf(avg_pq - obj->peak.avg_pq) < epsilon)
1138 : : avg_pq = obj->peak.avg_pq;
1139 [ + - ]: 10 : if (fabsf(max_pq - obj->peak.max_pq) < epsilon)
1140 : : max_pq = obj->peak.max_pq;
1141 : : }
1142 : :
1143 : : // Use an IIR low-pass filter to smooth out the detected values
1144 [ + + ]: 14 : const float coeff = iir_coeff(params->smoothing_period);
1145 : 14 : obj->peak.avg_pq += coeff * (avg_pq - obj->peak.avg_pq);
1146 : 14 : obj->peak.max_pq += coeff * (max_pq - obj->peak.max_pq);
1147 : :
1148 : : // Scene change hysteresis
1149 [ + + + - ]: 14 : if (params->scene_threshold_low > 0 && params->scene_threshold_high > 0) {
1150 : : const float log10_pq = 1e-2f; // experimentally determined approximate
1151 : 12 : const float thresh_low = params->scene_threshold_low * log10_pq;
1152 : 12 : const float thresh_high = params->scene_threshold_high * log10_pq;
1153 : 12 : const float bias = (float) frame_wg_active / frame_wg_count;
1154 [ - + ]: 12 : const float delta = bias * fabsf(avg_pq - obj->peak.avg_pq);
1155 : : const float mix_coeff = pl_smoothstep(thresh_low, thresh_high, delta);
1156 : 12 : obj->peak.avg_pq = PL_MIX(obj->peak.avg_pq, avg_pq, mix_coeff);
1157 : 12 : obj->peak.max_pq = PL_MIX(obj->peak.max_pq, max_pq, mix_coeff);
1158 : : }
1159 : : }
1160 : :
1161 : 14 : bool pl_shader_detect_peak(pl_shader sh, struct pl_color_space csp,
1162 : : pl_shader_obj *state,
1163 : : const struct pl_peak_detect_params *params)
1164 : : {
1165 [ - + ]: 14 : params = PL_DEF(params, &pl_peak_detect_default_params);
1166 [ - + ]: 14 : if (!sh_require(sh, PL_SHADER_SIG_COLOR, 0, 0))
1167 : : return false;
1168 : :
1169 : 14 : pl_gpu gpu = SH_GPU(sh);
1170 [ + - - + ]: 14 : if (!gpu || gpu->limits.max_ssbo_size < sizeof(struct peak_buf_data)) {
1171 : 0 : PL_ERR(sh, "HDR peak detection requires a GPU with support for at "
1172 : : "least %zu bytes of SSBO data (supported: %zu)",
1173 : : sizeof(struct peak_buf_data), gpu ? gpu->limits.max_ssbo_size : 0);
1174 : 0 : return false;
1175 : : }
1176 : :
1177 [ + + + - ]: 14 : const bool use_histogram = params->percentile > 0 && params->percentile < 100;
1178 : : size_t shmem_req = 3 * sizeof(uint32_t);
1179 : : if (use_histogram)
1180 : : shmem_req += sizeof(uint32_t[HIST_BINS]);
1181 : :
1182 [ - + ]: 14 : if (!sh_try_compute(sh, 16, 16, true, shmem_req)) {
1183 : 0 : PL_ERR(sh, "HDR peak detection requires compute shaders with support "
1184 : : "for at least %zu bytes of shared memory! (avail: %zu)",
1185 : : shmem_req, sh_glsl(sh).max_shmem_size);
1186 : 0 : return false;
1187 : : }
1188 : :
1189 : : struct sh_color_map_obj *obj;
1190 : 14 : obj = SH_OBJ(sh, state, PL_SHADER_OBJ_COLOR_MAP, struct sh_color_map_obj,
1191 : : sh_color_map_uninit);
1192 [ - + ]: 14 : if (!obj)
1193 : : return false;
1194 : :
1195 : : if (peak_detect_params_eq(&obj->peak.params, params)) {
1196 : 12 : update_peak_buf(gpu, obj, true); // prevent over-writing previous frame
1197 : : } else {
1198 : 2 : pl_reset_detected_peak(*state);
1199 : : }
1200 : :
1201 [ - + ]: 14 : pl_assert(!obj->peak.buf);
1202 : : static const struct peak_buf_data zero = {0};
1203 : :
1204 : 14 : retry_ssbo:
1205 [ - + ]: 14 : if (obj->peak.readback) {
1206 : 0 : obj->peak.buf = pl_buf_create(gpu, pl_buf_params(
1207 : : .size = sizeof(struct peak_buf_data),
1208 : : .storable = true,
1209 : : .initial_data = &zero,
1210 : : ));
1211 : : } else {
1212 : 14 : obj->peak.buf = pl_buf_create(gpu, pl_buf_params(
1213 : : .size = sizeof(struct peak_buf_data),
1214 : : .memory_type = PL_BUF_MEM_DEVICE,
1215 : : .host_readable = true,
1216 : : .storable = true,
1217 : : .initial_data = &zero,
1218 : : ));
1219 : : }
1220 : :
1221 [ - + - - ]: 14 : if (!obj->peak.buf && !obj->peak.readback) {
1222 : 0 : PL_WARN(sh, "Failed creating host-readable peak detection SSBO, "
1223 : : "retrying with fallback buffer");
1224 : 0 : obj->peak.readback = pl_buf_create(gpu, pl_buf_params(
1225 : : .size = sizeof(struct peak_buf_data),
1226 : : .host_readable = true,
1227 : : ));
1228 [ # # ]: 0 : if (obj->peak.readback)
1229 : 0 : goto retry_ssbo;
1230 : : }
1231 : :
1232 [ - + ]: 14 : if (!obj->peak.buf) {
1233 : 0 : SH_FAIL(sh, "Failed creating peak detection SSBO!");
1234 : 0 : return false;
1235 : : }
1236 : :
1237 : 14 : obj->peak.params = *params;
1238 : :
1239 : 14 : sh_desc(sh, (struct pl_shader_desc) {
1240 : : .desc = {
1241 : : .name = "PeakBuf",
1242 : : .type = PL_DESC_BUF_STORAGE,
1243 : : .access = PL_DESC_ACCESS_READWRITE,
1244 : : },
1245 : : .binding.object = obj->peak.buf,
1246 : : .buffer_vars = (struct pl_buffer_var *) peak_buf_vars,
1247 : : .num_buffer_vars = PL_ARRAY_SIZE(peak_buf_vars),
1248 : : });
1249 : :
1250 : : // For performance, we want to do as few atomic operations on global
1251 : : // memory as possible, so use an atomic in shmem for the work group.
1252 : 14 : ident_t wg_sum = sh_fresh(sh, "wg_sum"),
1253 : 14 : wg_max = sh_fresh(sh, "wg_max"),
1254 : 14 : wg_black = sh_fresh(sh, "wg_black"),
1255 : : wg_hist = NULL_IDENT;
1256 : 14 : GLSLH("shared uint "$", "$", "$"; \n", wg_sum, wg_max, wg_black);
1257 [ - + ]: 14 : if (use_histogram) {
1258 : 0 : wg_hist = sh_fresh(sh, "wg_hist");
1259 : 0 : GLSLH("shared uint "$"[%u]; \n", wg_hist, HIST_BINS);
1260 : : }
1261 : :
1262 : 14 : sh_describe(sh, "peak detection");
1263 : 6 : {
1264 : 14 : const struct __attribute__((__packed__)) {
1265 : : unsigned slices;
1266 : : unsigned hist_bins;
1267 : : ident_t wg_sum;
1268 : : ident_t wg_max;
1269 : : ident_t wg_black;
1270 : : ident_t wg_hist;
1271 : : bool use_histogram;
1272 : 14 : } _glsl_1263_args = {
1273 : 0 : #line 1268
1274 : : .slices = SLICES,
1275 : 6 : #line 1269
1276 : 6 : .hist_bins = HIST_BINS,
1277 : 6 : #line 1271
1278 : : .wg_sum = wg_sum,
1279 : 14 : #line 1271
1280 [ - + ]: 20 : .wg_max = wg_max,
1281 : 6 : #line 1271
1282 : 14 : .wg_black = wg_black,
1283 : 14 : #line 1274
1284 : 6 : .wg_hist = wg_hist,
1285 : 28 : #line 1272
1286 : : .use_histogram = use_histogram,
1287 : : };
1288 : : #line 1263
1289 : : size_t _glsl_1263_fn(void *, pl_str *, const uint8_t *);
1290 : : pl_str_builder_append(sh->buffers[SH_BUF_BODY], _glsl_1263_fn,
1291 : : &_glsl_1263_args, sizeof(_glsl_1263_args));
1292 : 24 : }
1293 : 12 : #line 1277
1294 : 4 :
1295 : 6 : // Decode color into linear light representation
1296 : 6 : pl_color_space_infer(&csp);
1297 : 0 : pl_shader_linearize(sh, &csp);
1298 : 6 :
1299 [ - + ]: 6 : bool has_subgroups = sh_glsl(sh).subgroup_size > 0;
1300 : : const float cutoff = fmaxf(params->black_cutoff, 0.0f) * 1e-2f;
1301 [ + + ]: 6 : {
1302 : : const struct __attribute__((__packed__)) {
1303 : : float pl_color_sdr_white_10000_0;
1304 : : float pq_m1;
1305 : : float pq_c1;
1306 [ - - ]: 14 : float pq_c2;
1307 : : float pq_c3;
1308 : : float pq_m2;
1309 : 0 : float pq_max;
1310 : 0 : int pq_bits_hist_bits;
1311 : 0 : int hist_bias;
1312 : : int hist_bins_1;
1313 : : unsigned hist_bins;
1314 : 3 : ident_t sh_luma_coeffs_sh_csp;
1315 : : ident_t cutoff_;
1316 [ + + ]: 6 : ident_t wg_hist;
1317 : 2 : ident_t wg_sum;
1318 : 3 : ident_t wg_max;
1319 [ + + ]: 3 : ident_t wg_black;
1320 : : bool cutoff;
1321 : : bool use_histogram;
1322 : 5 : bool has_subgroups;
1323 : 6 : } _glsl_1284_args = {
1324 : 2 : #line 1286
1325 : 3 : .pl_color_sdr_white_10000_0 = PL_COLOR_SDR_WHITE / 10000.0,
1326 [ + + ]: 3 : #line 1287
1327 : : .pq_m1 = PQ_M1,
1328 : 5 : #line 1288
1329 : 3 : .pq_c1 = PQ_C1,
1330 : : #line 1288
1331 : 2 : .pq_c2 = PQ_C2,
1332 [ + + ]: 8 : #line 1289
1333 : : .pq_c3 = PQ_C3,
1334 : : #line 1290
1335 : : .pq_m2 = PQ_M2,
1336 [ - + ]: 6 : #line 1293
1337 [ # # ]: 0 : .pq_max = PQ_MAX,
1338 : : #line 1297
1339 : 0 : .pq_bits_hist_bits = PQ_BITS - HIST_BITS,
1340 : 0 : #line 1298
1341 : : .hist_bias = HIST_BIAS,
1342 : : #line 1299
1343 : : .hist_bins_1 = HIST_BINS - 1,
1344 : 0 : #line 1340
1345 : : .hist_bins = HIST_BINS,
1346 : : #line 1285
1347 : : .sh_luma_coeffs_sh_csp = sh_luma_coeffs(sh, &csp),
1348 : : #line 1292
1349 : : .cutoff_ = sh_const_float(sh, "cutoff", cutoff),
1350 : : #line 1304
1351 : : .wg_hist = wg_hist,
1352 : : #line 1319
1353 : : .wg_sum = wg_sum,
1354 : : #line 1320
1355 : : .wg_max = wg_max,
1356 : : #line 1322
1357 : 14 : .wg_black = wg_black,
1358 : 6 : #line 1291
1359 : 6 : .cutoff = cutoff,
1360 : 791 : #line 1296
1361 : : .use_histogram = use_histogram,
1362 : : #line 1300
1363 [ + + - + ]: 791 : .has_subgroups = has_subgroups,
1364 : : };
1365 : : #line 1284
1366 : 782 : size_t _glsl_1284_fn(void *, pl_str *, const uint8_t *);
1367 : 782 : pl_str_builder_append(sh->buffers[SH_BUF_BODY], _glsl_1284_fn,
1368 [ + + ]: 782 : &_glsl_1284_args, sizeof(_glsl_1284_args));
1369 : : }
1370 : : #line 1356
1371 : 14 :
1372 : 14 : return true;
1373 : 14 : }
1374 : :
1375 : : bool pl_get_detected_hdr_metadata(const pl_shader_obj state,
1376 : 783 : struct pl_hdr_metadata *out)
1377 : : {
1378 [ + + + - ]: 783 : if (!state || state->type != PL_SHADER_OBJ_COLOR_MAP)
1379 : : return false;
1380 : :
1381 : 774 : struct sh_color_map_obj *obj = state->priv;
1382 : 774 : update_peak_buf(state->gpu, obj, false);
1383 : 774 : if (!obj->peak.avg_pq)
1384 : 774 : return false;
1385 : 774 :
1386 : : out->max_pq_y = obj->peak.max_pq;
1387 : : out->avg_pq_y = obj->peak.avg_pq;
1388 : 0 : return true;
1389 : : }
1390 [ # # ]: 0 :
1391 : : void pl_reset_detected_peak(pl_shader_obj state)
1392 : : {
1393 : 0 : if (!state || state->type != PL_SHADER_OBJ_COLOR_MAP)
1394 : 0 : return;
1395 : 0 :
1396 : : struct sh_color_map_obj *obj = state->priv;
1397 : : pl_buf readback = obj->peak.readback;
1398 : : pl_buf_destroy(state->gpu, &obj->peak.buf);
1399 : : memset(&obj->peak, 0, sizeof(obj->peak));
1400 : : obj->peak.readback = readback;
1401 : : }
1402 : :
1403 : : void pl_shader_extract_features(pl_shader sh, struct pl_color_space csp)
1404 : : {
1405 : : if (!sh_require(sh, PL_SHADER_SIG_COLOR, 0, 0))
1406 : : return;
1407 : :
1408 : : sh_describe(sh, "feature extraction");
1409 : : pl_shader_linearize(sh, &csp);
1410 : : GLSL("// pl_shader_extract_features \n"
1411 : : "{ \n"
1412 : : "vec3 lms = %f * "$" * color.rgb; \n"
1413 : : "lms = pow(max(lms, 0.0), vec3(%f)); \n"
1414 : 8 : "lms = (vec3(%f) + %f * lms) \n"
1415 : : " / (vec3(1.0) + %f * lms); \n"
1416 [ - + + - ]: 8 : "lms = pow(lms, vec3(%f)); \n"
1417 : : "float I = dot(vec3(%f, %f, %f), lms); \n"
1418 [ - + + - ]: 8 : "color = vec4(I, 0.0, 0.0, 1.0); \n"
1419 : : "} \n",
1420 : : PL_COLOR_SDR_WHITE / 10000,
1421 : 16 : SH_MAT3(pl_ipt_rgb2lms(pl_raw_primaries_get(csp.primaries))),
1422 : 8 : PQ_M1, PQ_C1, PQ_C2, PQ_C3, PQ_M2,
1423 : 8 : pl_ipt_lms2ipt.m[0][0], pl_ipt_lms2ipt.m[0][1], pl_ipt_lms2ipt.m[0][2]);
1424 : 8 : }
1425 : 8 :
1426 : : const struct pl_color_map_params pl_color_map_default_params = { PL_COLOR_MAP_DEFAULTS };
1427 : : const struct pl_color_map_params pl_color_map_high_quality_params = { PL_COLOR_MAP_HQ_DEFAULTS };
1428 : :
1429 : 4 : static ident_t rect_pos(pl_shader sh, pl_rect2df rc)
1430 : : {
1431 : : if (!rc.x0 && !rc.x1)
1432 [ - + ]: 4 : rc.x1 = 1.0f;
1433 [ - + ]: 4 : if (!rc.y0 && !rc.y1)
1434 : : rc.y1 = 1.0f;
1435 : 4 :
1436 : : return sh_attr_vec2(sh, "tone_map_coords", &(pl_rect2df) {
1437 : : .x0 = -rc.x0 / (rc.x1 - rc.x0),
1438 : : .x1 = (1.0f - rc.x0) / (rc.x1 - rc.x0),
1439 : : .y0 = -rc.y1 / (rc.y0 - rc.y1),
1440 : : .y1 = (1.0f - rc.y1) / (rc.y0 - rc.y1),
1441 : : });
1442 : : }
1443 : :
1444 : : static void visualize_tone_map(pl_shader sh, pl_rect2df rc, float alpha,
1445 : : const struct pl_tone_map_params *params)
1446 : : {
1447 : : pl_assert(params->input_scaling == PL_HDR_PQ);
1448 : : pl_assert(params->output_scaling == PL_HDR_PQ);
1449 : :
1450 : : GLSL("// Visualize tone mapping \n"
1451 : : "{ \n"
1452 : : "vec2 pos = "$"; \n"
1453 : : "if (min(pos.x, pos.y) >= 0.0 && \n" // visualizer rect
1454 : : " max(pos.x, pos.y) <= 1.0) \n"
1455 : : "{ \n"
1456 : : "float xmin = "$"; \n"
1457 : : "float xmax = "$"; \n"
1458 : : "float xavg = "$"; \n"
1459 : : "float ymin = "$"; \n"
1460 : : "float ymax = "$"; \n"
1461 : : "float alpha = 0.8 * "$"; \n"
1462 : : "vec3 viz = color.rgb; \n"
1463 : : "float vv = tone_map(pos.x); \n"
1464 : : // Color based on region
1465 : : "if (pos.x < xmin || pos.x > xmax) { \n" // outside source
1466 : : "} else if (pos.y < ymin || pos.y > ymax) {\n" // outside target
1467 : : " if (pos.y < xmin || pos.y > xmax) { \n" // and also source
1468 : : " viz = vec3(0.1, 0.1, 0.5); \n"
1469 : : " } else { \n"
1470 : : " viz = vec3(0.2, 0.05, 0.05); \n" // but inside source
1471 : : " } \n"
1472 : : "} else { \n" // inside domain
1473 : : " if (abs(pos.x - pos.y) < 1e-3) { \n" // main diagonal
1474 : : " viz = vec3(0.2); \n"
1475 : : " } else if (pos.y < vv) { \n" // inside function
1476 : : " alpha *= 0.6; \n"
1477 : : " viz = vec3(0.05); \n"
1478 : : " if (vv > pos.x && pos.y > pos.x) \n" // output brighter than input
1479 : : " viz.rg = vec2(0.5, 0.7); \n"
1480 : : " } else { \n" // outside function
1481 : : " if (vv < pos.x && pos.y < pos.x) \n" // output darker than input
1482 : : " viz = vec3(0.0, 0.1, 0.2); \n"
1483 : : " } \n"
1484 : : " if (pos.y > xmax) { \n" // inverse tone-mapping region
1485 : : " vec3 hi = vec3(0.2, 0.5, 0.8); \n"
1486 : : " viz = mix(viz, hi, 0.5); \n"
1487 : : " } else if (pos.y < xmin) { \n" // black point region
1488 : 4 : " viz = mix(viz, vec3(0.0), 0.3); \n"
1489 : : " } \n"
1490 : 4 : " if (xavg > 0.0 && abs(pos.x - xavg) < 1e-3)\n" // source avg brightness
1491 : : " viz = vec3(0.5); \n"
1492 : : "} \n"
1493 : : "color.rgb = mix(color.rgb, viz, alpha); \n"
1494 : 4 : "} \n"
1495 : 4 : "} \n",
1496 : 4 : rect_pos(sh, rc),
1497 : : SH_FLOAT_DYN(params->input_min),
1498 : 4 : SH_FLOAT_DYN(params->input_max),
1499 : : SH_FLOAT_DYN(params->input_avg),
1500 : : SH_FLOAT(params->output_min),
1501 : : SH_FLOAT_DYN(params->output_max),
1502 : : SH_FLOAT_DYN(alpha));
1503 : : }
1504 : :
1505 : : static void visualize_gamut_map(pl_shader sh, pl_rect2df rc,
1506 : : ident_t lut, float hue, float theta,
1507 : : const struct pl_gamut_map_params *params)
1508 : : {
1509 : : ident_t ipt2lms = SH_MAT3(pl_ipt_ipt2lms);
1510 : : ident_t lms2rgb_src = SH_MAT3(pl_ipt_lms2rgb(¶ms->input_gamut));
1511 : : ident_t lms2rgb_dst = SH_MAT3(pl_ipt_lms2rgb(¶ms->output_gamut));
1512 : :
1513 : : GLSL("// Visualize gamut mapping \n"
1514 : : "vec2 pos = "$"; \n"
1515 : : "float pqmin = "$"; \n"
1516 : : "float pqmax = "$"; \n"
1517 : : "float rgbmin = "$"; \n"
1518 : : "float rgbmax = "$"; \n"
1519 : : "vec3 orig = ipt; \n"
1520 : : "if (min(pos.x, pos.y) >= 0.0 && \n"
1521 : : " max(pos.x, pos.y) <= 1.0) \n"
1522 : : "{ \n"
1523 : : // Source color to visualize
1524 : : "float mid = mix(pqmin, pqmax, 0.6); \n"
1525 : : "vec3 base = vec3(0.5, 0.0, 0.0); \n"
1526 : : "float hue = "$", theta = "$"; \n"
1527 : : "base.x = mix(base.x, mid, sin(theta)); \n"
1528 : : "mat3 rot1 = mat3(1.0, 0.0, 0.0, \n"
1529 : : " 0.0, cos(hue), sin(hue), \n"
1530 : : " 0.0, -sin(hue), cos(hue)); \n"
1531 : : "mat3 rot2 = mat3( cos(theta), 0.0, sin(theta), \n"
1532 : : " 0.0, 1.0, 0.0, \n"
1533 : : " -sin(theta), 0.0, cos(theta)); \n"
1534 : : "vec3 dir = vec3(pos.yx - vec2(0.5), 0.0); \n"
1535 : : "ipt = base + rot1 * rot2 * dir; \n"
1536 : : // Convert back to RGB (for gamut boundary testing)
1537 : : "lmspq = "$" * ipt; \n"
1538 : : "lms = pow(max(lmspq, 0.0), vec3(1.0/%f)); \n"
1539 : : "lms = max(lms - vec3(%f), 0.0) \n"
1540 : : " / (vec3(%f) - %f * lms); \n"
1541 : : "lms = pow(lms, vec3(1.0/%f)); \n"
1542 : : "lms *= %f; \n"
1543 : : // Check against src/dst gamut boundaries
1544 : : "vec3 rgbsrc = "$" * lms; \n"
1545 : : "vec3 rgbdst = "$" * lms; \n"
1546 : : "bool insrc, indst; \n"
1547 : : "insrc = all(lessThan(rgbsrc, vec3(rgbmax))) && \n"
1548 : : " all(greaterThan(rgbsrc, vec3(rgbmin))); \n"
1549 : : "indst = all(lessThan(rgbdst, vec3(rgbmax))) && \n"
1550 : : " all(greaterThan(rgbdst, vec3(rgbmin))); \n"
1551 : : // Sample from gamut mapping 3DLUT
1552 : : "idx.x = (ipt.x - pqmin) / (pqmax - pqmin); \n"
1553 : : "idx.y = 2.0 * length(ipt.yz); \n"
1554 : : "idx.z = %f * atan(ipt.z, ipt.y) + 0.5; \n"
1555 : : "vec3 mapped = "$"(idx).xyz; \n"
1556 : : "mapped.yz -= vec2(32768.0/65535.0); \n"
1557 : : "float mappedhue = atan(mapped.z, mapped.y); \n"
1558 : : "float mappedchroma = length(mapped.yz); \n"
1559 : : "ipt = mapped; \n"
1560 : : // Visualize gamuts
1561 : : "if (!insrc && !indst) { \n"
1562 : : " ipt = orig; \n"
1563 : : "} else if (insrc && !indst) { \n"
1564 : : " ipt.x -= 0.1; \n"
1565 : : "} else if (indst && !insrc) { \n"
1566 : : " ipt.x += 0.1; \n"
1567 : : "} \n"
1568 : : // Visualize iso-luminance and iso-hue lines
1569 : : "vec3 line; \n"
1570 : : "if (insrc && fract(50.0 * mapped.x) < 1e-1) { \n"
1571 : : " float k = smoothstep(0.1, 0.0, abs(sin(theta))); \n"
1572 : : " line.x = mix(mapped.x, 0.3, 0.5); \n"
1573 : : " line.yz = sqrt(length(mapped.yz)) * \n"
1574 : : " normalize(mapped.yz); \n"
1575 : : " ipt = mix(ipt, line, k); \n"
1576 : : "} \n"
1577 : : "if (insrc && fract(10.0 * (mappedhue - hue)) < 1e-1) {\n"
1578 : : " float k = smoothstep(0.3, 0.0, abs(cos(theta))); \n"
1579 : : " line.x = mapped.x - 0.05; \n"
1580 : : " line.yz = 1.2 * mapped.yz; \n"
1581 : : " ipt = mix(ipt, line, k); \n"
1582 : : "} \n"
1583 : : "if (insrc && fract(100.0 * mappedchroma) < 1e-1) { \n"
1584 : : " line.x = mapped.x + 0.1; \n"
1585 : : " line.yz = 0.4 * mapped.yz; \n"
1586 : 4 : " ipt = mix(ipt, line, 0.5); \n"
1587 : : "} \n"
1588 : 10 : "} \n",
1589 : : rect_pos(sh, rc),
1590 : 10 : SH_FLOAT(params->min_luma), SH_FLOAT(params->max_luma),
1591 : 10 : SH_FLOAT(pl_hdr_rescale(PL_HDR_PQ, PL_HDR_NORM, params->min_luma)),
1592 : 10 : SH_FLOAT(pl_hdr_rescale(PL_HDR_PQ, PL_HDR_NORM, params->max_luma)),
1593 : : SH_FLOAT_DYN(hue), SH_FLOAT_DYN(theta),
1594 : 16 : ipt2lms,
1595 : : PQ_M2, PQ_C1, PQ_C2, PQ_C3, PQ_M1,
1596 : 16 : 10000 / PL_COLOR_SDR_WHITE,
1597 : 16 : lms2rgb_src,
1598 : 16 : lms2rgb_dst,
1599 : 16 : 0.5f / M_PI,
1600 : : lut);
1601 : : }
1602 : :
1603 : : static void fill_tone_lut(void *data, const struct sh_lut_params *params)
1604 [ - + ]: 16 : {
1605 [ - + ]: 16 : const struct pl_tone_map_params *lut_params = params->priv;
1606 [ + + ]: 6291472 : pl_tone_map_generate(data, lut_params);
1607 : 6291456 : }
1608 : 6291456 :
1609 : 6291456 : static void fill_gamut_lut(void *data, const struct sh_lut_params *params)
1610 : 6291456 : {
1611 : 6291456 : const struct pl_gamut_map_params *lut_params = params->priv;
1612 : : const int lut_size = params->width * params->height * params->depth;
1613 : : void *tmp = pl_alloc(NULL, lut_size * sizeof(float) * lut_params->lut_stride);
1614 : 16 : pl_gamut_map_generate(tmp, lut_params);
1615 : 16 :
1616 : : // Convert to 16-bit unsigned integer for GPU texture
1617 : 1881 : const float *in = tmp;
1618 : : uint16_t *out = data;
1619 : : pl_assert(lut_params->lut_stride == 3);
1620 [ + - ]: 1881 : pl_assert(params->comps == 4);
1621 : 1823 : for (int i = 0; i < lut_size; i++) {
1622 : : out[0] = roundf(in[0] * UINT16_MAX);
1623 : 1881 : out[1] = roundf(in[1] * UINT16_MAX + (UINT16_MAX >> 1));
1624 : : out[2] = roundf(in[2] * UINT16_MAX + (UINT16_MAX >> 1));
1625 [ + + ]: 1881 : in += 3;
1626 : 789 : out += 4;
1627 : 789 : }
1628 : :
1629 [ + - ]: 789 : pl_free(tmp);
1630 : : }
1631 : :
1632 : : void pl_shader_color_map_ex(pl_shader sh, const struct pl_color_map_params *params,
1633 : 1881 : const struct pl_color_map_args *args)
1634 [ + + ]: 1881 : {
1635 [ + + ]: 1823 : if (!sh_require(sh, PL_SHADER_SIG_COLOR, 0, 0))
1636 : 208 : return;
1637 : 1823 :
1638 : : struct pl_color_space src = args->src, dst = args->dst;
1639 : : struct sh_color_map_obj *obj = NULL;
1640 [ - + ]: 58 : if (args->state) {
1641 : 58 : pl_get_detected_hdr_metadata(*args->state, &src.hdr);
1642 : : obj = SH_OBJ(sh, args->state, PL_SHADER_OBJ_COLOR_MAP, struct sh_color_map_obj,
1643 : : sh_color_map_uninit);
1644 : 174 : if (!obj)
1645 [ + - ]: 58 : return;
1646 : : }
1647 : 58 :
1648 : : pl_color_space_infer_map(&src, &dst);
1649 : : if (pl_color_space_equal(&src, &dst)) {
1650 [ + - ]: 58 : if (args->prelinearized)
1651 : : pl_shader_delinearize(sh, &dst);
1652 : : return;
1653 : : }
1654 : 58 :
1655 : : params = PL_DEF(params, &pl_color_map_default_params);
1656 : : GLSL("// pl_shader_color_map \n"
1657 : : "{ \n");
1658 : :
1659 : : struct pl_tone_map_params tone = {
1660 : : .function = PL_DEF(params->tone_mapping_function, &pl_tone_map_clip),
1661 : : .constants = params->tone_constants,
1662 : : .param = params->tone_mapping_param,
1663 : 58 : .input_scaling = PL_HDR_PQ,
1664 : : .output_scaling = PL_HDR_PQ,
1665 : : .lut_size = PL_DEF(params->lut_size, pl_color_map_default_params.lut_size),
1666 : : .hdr = src.hdr,
1667 : : };
1668 : :
1669 : : pl_color_space_nominal_luma_ex(pl_nominal_luma_params(
1670 : : .color = &src,
1671 : 58 : .metadata = params->metadata,
1672 : : .scaling = tone.input_scaling,
1673 : : .out_min = &tone.input_min,
1674 [ + + ]: 58 : .out_max = &tone.input_max,
1675 : 36 : .out_avg = &tone.input_avg,
1676 [ + + ]: 58 : ));
1677 : 36 :
1678 : : pl_color_space_nominal_luma_ex(pl_nominal_luma_params(
1679 [ + + ]: 58 : .color = &dst,
1680 : : .metadata = PL_HDR_METADATA_HDR10,
1681 : : .scaling = tone.output_scaling,
1682 [ + + ]: 92 : .out_min = &tone.output_min,
1683 : : .out_max = &tone.output_max,
1684 : : ));
1685 : :
1686 : 290 : pl_tone_map_params_infer(&tone);
1687 [ + - ]: 58 :
1688 : : // Round sufficiently similar values
1689 : : if (fabs(tone.input_max - tone.output_max) < 1e-6)
1690 : : tone.output_max = tone.input_max;
1691 [ + - ]: 58 : if (fabs(tone.input_min - tone.output_min) < 1e-6)
1692 [ + - ]: 58 : tone.output_min = tone.input_min;
1693 [ + - ]: 58 :
1694 : : if (!params->inverse_tone_mapping) {
1695 : : // Never exceed the source unless requested, but still allow
1696 : : // black point adaptation
1697 : : tone.output_max = PL_MIN(tone.output_max, tone.input_max);
1698 : 58 : }
1699 : :
1700 : : const int *lut3d_size_def = pl_color_map_default_params.lut3d_size;
1701 : : struct pl_gamut_map_params gamut = {
1702 : : .function = PL_DEF(params->gamut_mapping, &pl_gamut_map_clip),
1703 : : .constants = params->gamut_constants,
1704 : : .input_gamut = src.hdr.prim,
1705 : 58 : .output_gamut = dst.hdr.prim,
1706 : : .lut_size_I = PL_DEF(params->lut3d_size[0], lut3d_size_def[0]),
1707 : : .lut_size_C = PL_DEF(params->lut3d_size[1], lut3d_size_def[1]),
1708 : : .lut_size_h = PL_DEF(params->lut3d_size[2], lut3d_size_def[2]),
1709 : : .lut_stride = 3,
1710 : : };
1711 : :
1712 : : float src_peak_static;
1713 : : pl_color_space_nominal_luma_ex(pl_nominal_luma_params(
1714 [ + - + - ]: 58 : .color = &src,
1715 [ + - ]: 58 : .metadata = PL_HDR_METADATA_HDR10,
1716 : 58 : .scaling = PL_HDR_PQ,
1717 : : .out_max = &src_peak_static,
1718 : : ));
1719 : :
1720 : : pl_color_space_nominal_luma_ex(pl_nominal_luma_params(
1721 : : .color = &dst,
1722 [ + - - - : 58 : .metadata = PL_HDR_METADATA_HDR10,
- - ]
1723 : 58 : .scaling = PL_HDR_PQ,
1724 [ - - + ]: 58 : .out_min = &gamut.min_luma,
1725 : : .out_max = &gamut.max_luma,
1726 : : ));
1727 : :
1728 : : // Clip the gamut mapping output to the input gamut if disabled
1729 : 0 : if (!params->gamut_expansion && gamut.function->bidirectional) {
1730 : 0 : if (pl_primaries_compatible(&gamut.input_gamut, &gamut.output_gamut)) {
1731 : 0 : gamut.output_gamut = pl_primaries_clip(&gamut.output_gamut,
1732 : 0 : &gamut.input_gamut);
1733 : 0 : }
1734 : 0 : }
1735 : :
1736 : : // Backwards compatibility with older API
1737 : 0 : switch (params->gamut_mode) {
1738 : 0 : case PL_GAMUT_CLIP:
1739 : 0 : switch (params->intent) {
1740 : 0 : case PL_INTENT_AUTO:
1741 : 0 : case PL_INTENT_PERCEPTUAL:
1742 : 0 : case PL_INTENT_RELATIVE_COLORIMETRIC:
1743 : 0 : break; // leave default
1744 : 0 : case PL_INTENT_SATURATION:
1745 : 0 : gamut.function = &pl_gamut_map_saturation;
1746 : : break;
1747 : 0 : case PL_INTENT_ABSOLUTE_COLORIMETRIC:
1748 : : gamut.function = &pl_gamut_map_absolute;
1749 : : break;
1750 : 58 : }
1751 [ + + ]: 58 : break;
1752 : : case PL_GAMUT_DARKEN:
1753 : : gamut.function = &pl_gamut_map_darken;
1754 [ + - ]: 24 : break;
1755 : 24 : case PL_GAMUT_WARN:
1756 [ - + ]: 24 : gamut.function = &pl_gamut_map_highlight;
1757 : 24 : break;
1758 : : case PL_GAMUT_DESATURATE:
1759 : : gamut.function = &pl_gamut_map_desaturate;
1760 : 58 : break;
1761 [ - + ]: 58 : case PL_GAMUT_MODE_COUNT:
1762 : 0 : pl_unreachable();
1763 : : }
1764 : :
1765 : : bool can_fast = !params->force_tone_mapping_lut;
1766 : 58 : if (!args->state) {
1767 : 58 : // No state object provided, forcibly disable advanced methods
1768 : : can_fast = true;
1769 [ + + ]: 58 : if (tone.function != &pl_tone_map_clip)
1770 : 42 : tone.function = &pl_tone_map_linear;
1771 : : if (gamut.function != &pl_gamut_map_clip)
1772 : 58 : gamut.function = &pl_gamut_map_saturation;
1773 : 58 : }
1774 : 58 :
1775 : 58 : pl_fmt gamut_fmt = pl_find_fmt(SH_GPU(sh), PL_FMT_UNORM, 4, 16, 16, PL_FMT_CAP_LINEAR);
1776 : : if (!gamut_fmt) {
1777 [ + + - + : 58 : gamut.function = &pl_gamut_map_saturation;
- - ]
1778 : 0 : can_fast = true;
1779 : 0 : }
1780 : 0 :
1781 : 0 : bool need_tone_map = !pl_tone_map_params_noop(&tone);
1782 : 0 : bool need_gamut_map = !pl_gamut_map_params_noop(&gamut);
1783 : :
1784 : : if (!args->prelinearized)
1785 : : pl_shader_linearize(sh, &src);
1786 : :
1787 [ + + ]: 58 : pl_matrix3x3 rgb2lms = pl_ipt_rgb2lms(pl_raw_primaries_get(src.primaries));
1788 [ - + ]: 28 : pl_matrix3x3 lms2rgb = pl_ipt_lms2rgb(pl_raw_primaries_get(dst.primaries));
1789 : 0 : ident_t lms2ipt = SH_MAT3(pl_ipt_lms2ipt);
1790 : 0 : ident_t ipt2lms = SH_MAT3(pl_ipt_ipt2lms);
1791 : 0 :
1792 : : if (need_gamut_map && gamut.function == &pl_gamut_map_saturation && can_fast) {
1793 : 28 : const pl_matrix3x3 lms2src = pl_ipt_lms2rgb(&gamut.input_gamut);
1794 : : const pl_matrix3x3 dst2lms = pl_ipt_rgb2lms(&gamut.output_gamut);
1795 : : sh_describe(sh, "gamut map (saturation)");
1796 : : pl_matrix3x3_mul(&lms2rgb, &dst2lms);
1797 : 30 : pl_matrix3x3_mul(&lms2rgb, &lms2src);
1798 : : need_gamut_map = false;
1799 : : }
1800 : :
1801 : : // Fast path: simply convert between primaries (if needed)
1802 : : if (!need_tone_map && !need_gamut_map) {
1803 : : if (src.primaries != dst.primaries) {
1804 : : sh_describe(sh, "colorspace conversion");
1805 : : pl_matrix3x3_mul(&lms2rgb, &rgb2lms);
1806 : : GLSL("color.rgb = "$" * color.rgb; \n", SH_MAT3(lms2rgb));
1807 : : }
1808 : : goto done;
1809 : : }
1810 [ - + ]: 30 :
1811 : : // Full path: convert input from normalized RGB to IPT
1812 : 0 : GLSL("vec3 lms = "$" * color.rgb; \n"
1813 : : "vec3 lmspq = %f * lms; \n"
1814 : : "lmspq = pow(max(lmspq, 0.0), vec3(%f)); \n"
1815 : : "lmspq = (vec3(%f) + %f * lmspq) \n"
1816 : : " / (vec3(1.0) + %f * lmspq); \n"
1817 : : "lmspq = pow(lmspq, vec3(%f)); \n"
1818 : : "vec3 ipt = "$" * lmspq; \n"
1819 : : "float i_orig = ipt.x; \n",
1820 : : SH_MAT3(rgb2lms),
1821 : : PL_COLOR_SDR_WHITE / 10000,
1822 : : PQ_M1, PQ_C1, PQ_C2, PQ_C3, PQ_M2,
1823 [ + + ]: 30 : lms2ipt);
1824 : 22 :
1825 : 22 : if (params->show_clipping) {
1826 : 22 : const float eps = 1e-6f;
1827 : 22 : GLSL("bool clip_hi, clip_lo; \n"
1828 : : "clip_hi = any(greaterThan(color.rgb, vec3("$"))); \n"
1829 [ - + - - ]: 22 : "clip_lo = any(lessThan(color.rgb, vec3("$"))); \n"
1830 : : "clip_hi = clip_hi || ipt.x > "$"; \n"
1831 : 0 : "clip_lo = clip_lo || ipt.x < "$"; \n",
1832 : : SH_FLOAT_DYN(pl_hdr_rescale(PL_HDR_PQ, PL_HDR_NORM, tone.input_max) + eps),
1833 : : SH_FLOAT(pl_hdr_rescale(PL_HDR_PQ, PL_HDR_NORM, tone.input_min) - eps),
1834 : : SH_FLOAT_DYN(tone.input_max + eps),
1835 [ - + - - ]: 22 : SH_FLOAT(tone.input_min - eps));
1836 : : }
1837 : 0 :
1838 : 0 : if (need_tone_map) {
1839 : : const struct pl_tone_map_function *fun = tone.function;
1840 : 0 : sh_describef(sh, "%s tone map (%.0f -> %.0f)", fun->name,
1841 : 0 : pl_hdr_rescale(PL_HDR_PQ, PL_HDR_NITS, tone.input_max),
1842 : : pl_hdr_rescale(PL_HDR_PQ, PL_HDR_NITS, tone.output_max));
1843 : :
1844 : : if (fun == &pl_tone_map_clip && can_fast) {
1845 : :
1846 : : GLSL("#define tone_map(x) clamp((x), "$", "$") \n",
1847 : : SH_FLOAT(tone.input_min),
1848 : : SH_FLOAT_DYN(tone.input_max));
1849 : :
1850 : : } else if (fun == &pl_tone_map_linear && can_fast) {
1851 : :
1852 : : const float gain = tone.constants.exposure;
1853 : : const float scale = tone.input_max - tone.input_min;
1854 : 0 :
1855 : : ident_t linfun = sh_fresh(sh, "linear_pq");
1856 : : GLSLH("float "$"(float x) { \n"
1857 : : // Stretch the input range (while clipping)
1858 [ - + ]: 22 : " x = "$" * x + "$"; \n"
1859 : 22 : " x = clamp(x, 0.0, 1.0); \n"
1860 : : " x = "$" * x + "$"; \n"
1861 : : " return x; \n"
1862 : : "} \n",
1863 : : linfun,
1864 : : SH_FLOAT_DYN(gain / scale),
1865 : : SH_FLOAT_DYN(-gain / scale * tone.input_min),
1866 : : SH_FLOAT_DYN(tone.output_max - tone.output_min),
1867 : : SH_FLOAT(tone.output_min));
1868 : :
1869 : : GLSL("#define tone_map(x) ("$"(x)) \n", linfun);
1870 : :
1871 : 22 : } else {
1872 [ - + ]: 22 :
1873 : 0 : pl_assert(obj);
1874 : 0 : ident_t lut = sh_lut(sh, sh_lut_params(
1875 : : .object = &obj->tone.lut,
1876 : : .var_type = PL_VAR_FLOAT,
1877 : 22 : .lut_type = SH_LUT_AUTO,
1878 : 22 : .method = SH_LUT_LINEAR,
1879 : : .width = tone.lut_size,
1880 : : .comps = 1,
1881 : : .update = !pl_tone_map_params_equal(&tone, &obj->tone.params),
1882 : : .dynamic = tone.input_avg > 0, // dynamic metadata
1883 : : .fill = fill_tone_lut,
1884 : 22 : .priv = &tone,
1885 [ + + - + : 22 : ));
- - ]
1886 : : obj->tone.params = tone;
1887 : 0 : if (!lut) {
1888 : : SH_FAIL(sh, "Failed generating tone-mapping LUT!");
1889 : : return;
1890 : : }
1891 : :
1892 : 0 : const float lut_range = tone.input_max - tone.input_min;
1893 : : GLSL("#define tone_map(x) ("$"("$" * (x) + "$")) \n",
1894 : : lut, SH_FLOAT_DYN(1.0f / lut_range),
1895 : : SH_FLOAT_DYN(-tone.input_min / lut_range));
1896 : :
1897 : : }
1898 : :
1899 : : bool need_recovery = tone.input_max >= tone.output_max;
1900 : : if (need_recovery && params->contrast_recovery && args->feature_map) {
1901 : : ident_t pos, pt;
1902 : : ident_t lowres = sh_bind(sh, args->feature_map, PL_TEX_ADDRESS_CLAMP,
1903 : : PL_TEX_SAMPLE_LINEAR, "feature_map",
1904 : : NULL, &pos, &pt);
1905 : :
1906 : : // Obtain HF detail map from bicubic interpolation of LF features
1907 : : GLSL("vec2 lpos = "$"; \n"
1908 : : "vec2 lpt = "$"; \n"
1909 : : "vec2 lsize = vec2(textureSize("$", 0)); \n"
1910 : : "vec2 frac = fract(lpos * lsize + vec2(0.5)); \n"
1911 : : "vec2 frac2 = frac * frac; \n"
1912 : : "vec2 inv = vec2(1.0) - frac; \n"
1913 : : "vec2 inv2 = inv * inv; \n"
1914 : : "vec2 w0 = 1.0/6.0 * inv2 * inv; \n"
1915 : : "vec2 w1 = 2.0/3.0 - 0.5 * frac2 * (2.0 - frac); \n"
1916 : : "vec2 w2 = 2.0/3.0 - 0.5 * inv2 * (2.0 - inv); \n"
1917 : : "vec2 w3 = 1.0/6.0 * frac2 * frac; \n"
1918 : : "vec4 g = vec4(w0 + w1, w2 + w3); \n"
1919 : : "vec4 h = vec4(w1, w3) / g + inv.xyxy; \n"
1920 : : "h.xy -= vec2(2.0); \n"
1921 : : "vec4 p = lpos.xyxy + lpt.xyxy * h; \n"
1922 : : "float l00 = textureLod("$", p.xy, 0.0).r; \n"
1923 : : "float l01 = textureLod("$", p.xw, 0.0).r; \n"
1924 : : "float l0 = mix(l01, l00, g.y); \n"
1925 : : "float l10 = textureLod("$", p.zy, 0.0).r; \n"
1926 : : "float l11 = textureLod("$", p.zw, 0.0).r; \n"
1927 : : "float l1 = mix(l11, l10, g.y); \n"
1928 : : "float luma = mix(l1, l0, g.x); \n"
1929 : 22 : // Mix low-resolution tone mapped image with high-resolution
1930 : : // tone mapped image according to desired strength.
1931 : : "float highres = clamp(ipt.x, 0.0, 1.0); \n"
1932 : : "float lowres = clamp(luma, 0.0, 1.0); \n"
1933 : : "float detail = highres - lowres; \n"
1934 : : "float base = tone_map(highres); \n"
1935 : 22 : "float sharp = tone_map(lowres) + detail; \n"
1936 : : "ipt.x = clamp(mix(base, sharp, "$"), "$", "$"); \n",
1937 : : pos, pt, lowres,
1938 : : lowres, lowres, lowres, lowres,
1939 : : SH_FLOAT(params->contrast_recovery),
1940 [ + - ]: 30 : SH_FLOAT(tone.output_min), SH_FLOAT_DYN(tone.output_max));
1941 : 30 :
1942 : 30 : } else {
1943 : :
1944 [ - + ]: 30 : GLSL("ipt.x = tone_map(ipt.x); \n");
1945 [ + - ]: 60 : }
1946 : :
1947 : : // Avoid raising saturation excessively when raising brightness, and
1948 : : // also desaturate when reducing brightness greatly to account for the
1949 : : // reduction in gamut volume.
1950 : : GLSL("vec2 hull = vec2(i_orig, ipt.x); \n"
1951 : : "hull = ((hull - 6.0) * hull + 9.0) * hull; \n"
1952 : : "ipt.yz *= min(i_orig / ipt.x, hull.y / hull.x); \n");
1953 : : }
1954 : :
1955 : : if (need_gamut_map) {
1956 : : const struct pl_gamut_map_function *fun = gamut.function;
1957 : : sh_describef(sh, "gamut map (%s)", fun->name);
1958 : :
1959 : : pl_assert(obj);
1960 [ - + ]: 30 : ident_t lut = sh_lut(sh, sh_lut_params(
1961 : 0 : .object = &obj->gamut.lut,
1962 : 0 : .var_type = PL_VAR_FLOAT,
1963 : : .lut_type = SH_LUT_TEXTURE,
1964 : : .fmt = gamut_fmt,
1965 : : .method = params->lut3d_tricubic ? SH_LUT_CUBIC : SH_LUT_LINEAR,
1966 : 30 : .width = gamut.lut_size_I,
1967 : 30 : .height = gamut.lut_size_C,
1968 : : .depth = gamut.lut_size_h,
1969 : : .comps = 4,
1970 : : .signature = gamut_map_signature(&gamut),
1971 : : .cache = SH_CACHE(sh),
1972 : : .fill = fill_gamut_lut,
1973 : : .priv = &gamut,
1974 : : ));
1975 : : if (!lut) {
1976 : : SH_FAIL(sh, "Failed generating gamut-mapping LUT!");
1977 [ - + ]: 30 : return;
1978 : 0 : }
1979 : :
1980 : : // 3D LUT lookup (in ICh space)
1981 : : const float lut_range = gamut.max_luma - gamut.min_luma;
1982 [ + + ]: 30 : GLSL("vec3 idx; \n"
1983 : 4 : "idx.x = "$" * ipt.x + "$"; \n"
1984 : 4 : "idx.y = 2.0 * length(ipt.yz); \n"
1985 : : "idx.z = %f * atan(ipt.z, ipt.y) + 0.5;\n"
1986 : : "ipt = "$"(idx).xyz; \n"
1987 : : "ipt.yz -= vec2(32768.0/65535.0); \n",
1988 : : SH_FLOAT(1.0f / lut_range),
1989 : : SH_FLOAT(-gamut.min_luma / lut_range),
1990 : 30 : 0.5f / M_PI, lut);
1991 : :
1992 : : if (params->show_clipping) {
1993 : : GLSL("clip_lo = clip_lo || any(lessThan(idx, vec3(0.0))); \n"
1994 : : "clip_hi = clip_hi || any(greaterThan(idx, vec3(1.0))); \n");
1995 : : }
1996 : :
1997 : : if (params->visualize_lut) {
1998 : : visualize_gamut_map(sh, params->visualize_rect, lut,
1999 : : params->visualize_hue, params->visualize_theta,
2000 : : &gamut);
2001 : : }
2002 [ - + ]: 30 : }
2003 : 0 :
2004 : : // Convert IPT back to linear RGB
2005 : : GLSL("lmspq = "$" * ipt; \n"
2006 : : "lms = pow(max(lmspq, 0.0), vec3(1.0/%f)); \n"
2007 : : "lms = max(lms - vec3(%f), 0.0) \n"
2008 : : " / (vec3(%f) - %f * lms); \n"
2009 : : "lms = pow(lms, vec3(1.0/%f)); \n"
2010 : : "lms *= %f; \n"
2011 : : "color.rgb = "$" * lms; \n",
2012 : : ipt2lms,
2013 : : PQ_M2, PQ_C1, PQ_C2, PQ_C3, PQ_M1,
2014 : : 10000 / PL_COLOR_SDR_WHITE,
2015 : : SH_MAT3(lms2rgb));
2016 : :
2017 : : if (params->show_clipping) {
2018 [ + + ]: 30 : GLSL("if (clip_hi) { \n"
2019 [ + + ]: 22 : " float k = dot(color.rgb, vec3(2.0 / 3.0)); \n"
2020 [ + - ]: 4 : " color.rgb = clamp(vec3(k) - color.rgb, 0.0, 1.0); \n"
2021 : 4 : " float cmin = min(min(color.r, color.g), color.b); \n"
2022 : : " float cmax = max(max(color.r, color.g), color.b); \n"
2023 : 22 : " float delta = cmax - cmin; \n"
2024 : : " vec3 sat = smoothstep(cmin - 1e-6, cmax, color.rgb); \n"
2025 : : " const vec3 red = vec3(1.0, 0.0, 0.0); \n"
2026 : 8 : " color.rgb = mix(red, sat, smoothstep(0.0, 0.3, delta)); \n"
2027 : 58 : "} else if (clip_lo) { \n"
2028 : 58 : " vec3 hi = vec3(0.0, 0.3, 0.3); \n"
2029 : : " color.rgb = mix(color.rgb, hi, 0.5); \n"
2030 : : "} \n");
2031 : : }
2032 : 0 :
2033 : : if (need_tone_map) {
2034 : : if (params->visualize_lut) {
2035 : : float alpha = need_gamut_map ? powf(cosf(params->visualize_theta), 5.0f) : 1.0f;
2036 : 0 : visualize_tone_map(sh, params->visualize_rect, alpha, &tone);
2037 : : }
2038 : : GLSL("#undef tone_map \n");
2039 : : }
2040 : :
2041 : : done:
2042 : : pl_shader_delinearize(sh, &dst);
2043 : 0 : GLSL("}\n");
2044 : : }
2045 : 4 :
2046 : : // Backwards compatibility wrapper around `pl_shader_color_map_ex`
2047 : : void pl_shader_color_map(pl_shader sh, const struct pl_color_map_params *params,
2048 [ + - ]: 4 : struct pl_color_space src, struct pl_color_space dst,
2049 : 0 : pl_shader_obj *state, bool prelinearized)
2050 [ + - + - ]: 4 : {
2051 : : pl_shader_color_map_ex(sh, params, pl_color_map_args(
2052 : : .src = src,
2053 : 4 : .dst = dst,
2054 : 4 : .prelinearized = prelinearized,
2055 : 4 : .state = state,
2056 : : .feature_map = NULL
2057 : 4 : ));
2058 : 4 : }
2059 : :
2060 : : void pl_shader_cone_distort(pl_shader sh, struct pl_color_space csp,
2061 : 4 : const struct pl_cone_params *params)
2062 : 8 : {
2063 : : if (!sh_require(sh, PL_SHADER_SIG_COLOR, 0, 0))
2064 : : return;
2065 : : if (!params || !params->cones)
2066 : : return;
2067 : 4 :
2068 : 4 : sh_describe(sh, "cone distortion");
2069 : : GLSL("// pl_shader_cone_distort\n");
2070 : : GLSL("{\n");
2071 : :
2072 : : pl_color_space_infer(&csp);
2073 : : pl_shader_linearize(sh, &csp);
2074 : :
2075 : : pl_matrix3x3 cone_mat;
2076 : : cone_mat = pl_get_cone_matrix(params, pl_raw_primaries_get(csp.primaries));
2077 : : GLSL("color.rgb = "$" * color.rgb; \n", sh_var(sh, (struct pl_shader_var) {
2078 : : .var = pl_var_mat3("cone_mat"),
2079 : : .data = PL_TRANSPOSE_3X3(cone_mat.m),
2080 : : }));
2081 : :
2082 : : pl_shader_delinearize(sh, &csp);
2083 : : GLSL("}\n");
2084 : : }
2085 : :
2086 : : // Auto-generated template functions:
2087 : : #line 1262
2088 : : size_t _glsl_1263_fn(void *alloc, pl_str *buf, const uint8_t *ptr);
2089 : : size_t _glsl_1263_fn(void *alloc, pl_str *buf, const uint8_t *ptr)
2090 : : {
2091 : : struct __attribute__((__packed__)) {
2092 : : unsigned slices;
2093 : : unsigned hist_bins;
2094 : : ident_t wg_sum;
2095 : : ident_t wg_max;
2096 : : ident_t wg_black;
2097 : : ident_t wg_hist;
2098 : : bool use_histogram;
2099 : : } vars;
2100 : : memcpy(&vars, ptr, sizeof(vars));
2101 : :
2102 : : #line 1263
2103 : : pl_str_append_asprintf_c(alloc, buf,
2104 : : "\n"
2105 : : "{\n"
2106 : : "const uint wg_size = gl_WorkGroupSize.x * gl_WorkGroupSize.y;\n"
2107 : : "const uint wg_idx = gl_WorkGroupID.y * gl_NumWorkGroups.x + gl_WorkGroupID.x;\n"
2108 : : "const uint local_idx = gl_LocalInvocationIndex;\n"
2109 : : "const uint slice = wg_idx %% uint(%u);\n"
2110 : : "const uint hist_base = slice * uint(%u);\n"
2111 : : "const vec4 color_orig = color;\n"
2112 : : "_%hx = _%hx = _%hx = 0u;\n",
2113 : : vars.slices,
2114 : : vars.hist_bins,
2115 : : vars.wg_sum,
2116 : : vars.wg_max,
2117 : : vars.wg_black
2118 : : );
2119 : :
2120 : : if (vars.use_histogram) {
2121 : : #line 1273
2122 : : pl_str_append_asprintf_c(alloc, buf,
2123 : : "for (uint i = local_idx; i < uint(%u); i += wg_size)\n"
2124 : : "_%hx[i] = 0u;\n",
2125 : : vars.hist_bins,
2126 : : vars.wg_hist
2127 : : );
2128 : :
2129 : : }
2130 : : #line 1276
2131 : : pl_str_append(alloc, buf, pl_str0(
2132 : : "barrier();\n"
2133 : : ));
2134 : :
2135 : :
2136 : : return sizeof(vars);
2137 : : }
2138 : : #line 1283
2139 : : size_t _glsl_1284_fn(void *alloc, pl_str *buf, const uint8_t *ptr);
2140 : : size_t _glsl_1284_fn(void *alloc, pl_str *buf, const uint8_t *ptr)
2141 : : {
2142 : : struct __attribute__((__packed__)) {
2143 : : float pl_color_sdr_white_10000_0;
2144 : : float pq_m1;
2145 : : float pq_c1;
2146 : : float pq_c2;
2147 : : float pq_c3;
2148 : : float pq_m2;
2149 : : float pq_max;
2150 : : int pq_bits_hist_bits;
2151 : : int hist_bias;
2152 : : int hist_bins_1;
2153 : : unsigned hist_bins;
2154 : : ident_t sh_luma_coeffs_sh_csp;
2155 : : ident_t cutoff_;
2156 : : ident_t wg_hist;
2157 : : ident_t wg_sum;
2158 : : ident_t wg_max;
2159 : : ident_t wg_black;
2160 : : bool cutoff;
2161 : : bool use_histogram;
2162 : : bool has_subgroups;
2163 : : } vars;
2164 : : memcpy(&vars, ptr, sizeof(vars));
2165 : :
2166 : : #line 1284
2167 : : pl_str_append_asprintf_c(alloc, buf,
2168 : : "\n"
2169 : : "float luma = dot(_%hx, color.rgb);\n"
2170 : : "luma *= float(%f);\n"
2171 : : "luma = pow(clamp(luma, 0.0, 1.0), float(%f));\n"
2172 : : "luma = (float(%f) + float(%f) * luma) /\n"
2173 : : "(1.0 + float(%f) * luma);\n"
2174 : : "luma = pow(luma, float(%f));\n",
2175 : : vars.sh_luma_coeffs_sh_csp,
2176 : : vars.pl_color_sdr_white_10000_0,
2177 : : vars.pq_m1,
2178 : : vars.pq_c1,
2179 : : vars.pq_c2,
2180 : : vars.pq_c3,
2181 : : vars.pq_m2
2182 : : );
2183 : :
2184 : : if (vars.cutoff)
2185 : : #line 1292
2186 : : pl_str_append_asprintf_c(alloc, buf,
2187 : : "luma *= smoothstep(0.0, _%hx, luma);\n",
2188 : : vars.cutoff_
2189 : : );
2190 : :
2191 : : #line 1293
2192 : : pl_str_append_asprintf_c(alloc, buf,
2193 : : "uint y_pq = uint(float(%f) * luma);\n"
2194 : : "\n",
2195 : : vars.pq_max
2196 : : );
2197 : :
2198 : : if (vars.use_histogram) {
2199 : : #line 1297
2200 : : pl_str_append_asprintf_c(alloc, buf,
2201 : : "int bin = int(y_pq) >> %d;\n"
2202 : : "bin -= %d;\n"
2203 : : "bin = clamp(bin, 0, %d);\n",
2204 : : vars.pq_bits_hist_bits,
2205 : : vars.hist_bias,
2206 : : vars.hist_bins_1
2207 : : );
2208 : :
2209 : : if (vars.has_subgroups) {
2210 : : #line 1301
2211 : : pl_str_append_asprintf_c(alloc, buf,
2212 : : "\n"
2213 : : "if (subgroupAllEqual(bin)) {\n"
2214 : : "if (subgroupElect())\n"
2215 : : "atomicAdd(_%hx[bin], gl_SubgroupSize);\n"
2216 : : "} else {\n"
2217 : : "atomicAdd(_%hx[bin], 1u);\n"
2218 : : "}\n",
2219 : : vars.wg_hist,
2220 : : vars.wg_hist
2221 : : );
2222 : :
2223 : : } else {
2224 : : #line 1309
2225 : : pl_str_append_asprintf_c(alloc, buf,
2226 : : "atomicAdd(_%hx[bin], 1u);\n",
2227 : : vars.wg_hist
2228 : : );
2229 : :
2230 : : }
2231 : : }
2232 : : if (vars.has_subgroups) {
2233 : : #line 1314
2234 : : pl_str_append(alloc, buf, pl_str0(
2235 : : "uint group_sum = subgroupAdd(y_pq);\n"
2236 : : "uint group_max = subgroupMax(y_pq);\n"
2237 : : ));
2238 : :
2239 : : if (vars.cutoff)
2240 : : #line 1317
2241 : : pl_str_append(alloc, buf, pl_str0(
2242 : : "uvec4 b = subgroupBallot(y_pq == 0u);\n"
2243 : : ));
2244 : :
2245 : : #line 1318
2246 : : pl_str_append_asprintf_c(alloc, buf,
2247 : : "if (subgroupElect()) {\n"
2248 : : "atomicAdd(_%hx, group_sum);\n"
2249 : : "atomicMax(_%hx, group_max);\n",
2250 : : vars.wg_sum,
2251 : : vars.wg_max
2252 : : );
2253 : :
2254 : : if (vars.cutoff)
2255 : : #line 1322
2256 : : pl_str_append_asprintf_c(alloc, buf,
2257 : : "atomicAdd(_%hx, subgroupBallotBitCount(b));\n",
2258 : : vars.wg_black
2259 : : );
2260 : :
2261 : : #line 1323
2262 : : pl_str_append(alloc, buf, pl_str0(
2263 : : "}\n"
2264 : : ));
2265 : :
2266 : : } else {
2267 : : #line 1325
2268 : : pl_str_append_asprintf_c(alloc, buf,
2269 : : "atomicAdd(_%hx, y_pq);\n"
2270 : : "atomicMax(_%hx, y_pq);\n",
2271 : : vars.wg_sum,
2272 : : vars.wg_max
2273 : : );
2274 : :
2275 : : if (vars.cutoff) {
2276 : : #line 1328
2277 : : pl_str_append_asprintf_c(alloc, buf,
2278 : : "if (y_pq == 0u)\n"
2279 : : "atomicAdd(_%hx, 1u);\n",
2280 : : vars.wg_black
2281 : : );
2282 : :
2283 : : }
2284 : : }
2285 : : #line 1332
2286 : : pl_str_append(alloc, buf, pl_str0(
2287 : : "barrier();\n"
2288 : : ));
2289 : :
2290 : : if (vars.use_histogram) {
2291 : : if (vars.cutoff) {
2292 : : #line 1336
2293 : : pl_str_append_asprintf_c(alloc, buf,
2294 : : "if (gl_LocalInvocationIndex == 0u)\n"
2295 : : "_%hx[0] -= _%hx;\n",
2296 : : vars.wg_hist,
2297 : : vars.wg_black
2298 : : );
2299 : :
2300 : : }
2301 : : #line 1339
2302 : : pl_str_append_asprintf_c(alloc, buf,
2303 : : "\n"
2304 : : "for (uint i = local_idx; i < uint(%u); i += wg_size)\n"
2305 : : "atomicAdd(frame_hist[hist_base + i], _%hx[i]);\n",
2306 : : vars.hist_bins,
2307 : : vars.wg_hist
2308 : : );
2309 : :
2310 : : }
2311 : : #line 1344
2312 : : pl_str_append_asprintf_c(alloc, buf,
2313 : : "\n"
2314 : : "if (gl_LocalInvocationIndex == 0u) {\n"
2315 : : "uint num = wg_size - _%hx;\n"
2316 : : "atomicAdd(frame_wg_count[slice], 1u);\n"
2317 : : "atomicAdd(frame_wg_active[slice], min(num, 1u));\n"
2318 : : "if (num > 0u) {\n"
2319 : : "atomicAdd(frame_sum_pq[slice], _%hx / num);\n"
2320 : : "atomicMax(frame_max_pq[slice], _%hx);\n"
2321 : : "}\n"
2322 : : "}\n"
2323 : : "color = color_orig;\n"
2324 : : "}\n",
2325 : : vars.wg_black,
2326 : : vars.wg_sum,
2327 : : vars.wg_max
2328 : : );
2329 : :
2330 : :
2331 : : return sizeof(vars);
2332 : : }
|