Coverage Report

Created: 2026-11-11 06:43

next uncovered line (L), next uncovered region (R), next uncovered branch (B)
/src/mpv/video/out/gpu/video_shaders.c
Line
Count
Source
1
/*
2
 * This file is part of mpv.
3
 *
4
 * mpv 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
 * mpv 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 mpv.  If not, see <http://www.gnu.org/licenses/>.
16
 */
17
18
#include <math.h>
19
20
#include <libplacebo/colorspace.h>
21
22
#include "video_shaders.h"
23
#include "video.h"
24
25
0
#define GLSL(x) gl_sc_add(sc, #x "\n");
26
0
#define GLSLF(...) gl_sc_addf(sc, __VA_ARGS__)
27
0
#define GLSLH(x) gl_sc_hadd(sc, #x "\n");
28
0
#define GLSLHF(...) gl_sc_haddf(sc, __VA_ARGS__)
29
30
// Set up shared/commonly used variables and macros
31
void sampler_prelude(struct gl_shader_cache *sc, int tex_num)
32
0
{
33
0
    GLSLF("#undef tex\n");
34
0
    GLSLF("#undef texmap\n");
35
0
    GLSLF("#define tex texture%d\n", tex_num);
36
0
    GLSLF("#define texmap texmap%d\n", tex_num);
37
0
    GLSLF("vec2 pos = texcoord%d;\n", tex_num);
38
0
    GLSLF("vec2 size = texture_size%d;\n", tex_num);
39
0
    GLSLF("vec2 pt = pixel_size%d;\n", tex_num);
40
0
}
41
42
static void pass_sample_separated_get_weights(struct gl_shader_cache *sc,
43
                                              struct scaler *scaler)
44
0
{
45
0
    gl_sc_uniform_texture(sc, "lut", scaler->lut);
46
0
    GLSLF("float ypos = LUT_POS(fcoord, %d.0);\n", scaler->lut->params.h);
47
48
0
    int N = scaler->kernel->size;
49
0
    int width = (N + 3) / 4; // round up
50
51
0
    GLSLF("float weights[%d];\n", N);
52
0
    for (int i = 0; i < N; i++) {
53
0
        if (i % 4 == 0)
54
0
            GLSLF("c = texture(lut, vec2(%f, ypos));\n", (i / 4 + 0.5) / width);
55
0
        GLSLF("weights[%d] = c[%d];\n", i, i % 4);
56
0
    }
57
0
}
58
59
// Handle a single pass (either vertical or horizontal). The direction is given
60
// by the vector (d_x, d_y). If the vector is 0, then planar interpolation is
61
// used instead (samples from texture0 through textureN)
62
void pass_sample_separated_gen(struct gl_shader_cache *sc, struct scaler *scaler,
63
                               int d_x, int d_y)
64
0
{
65
0
    int N = scaler->kernel->size;
66
0
    bool use_ar = scaler->conf.antiring > 0;
67
0
    bool planar = d_x == 0 && d_y == 0;
68
0
    GLSL(color = vec4(0.0);)
69
0
    GLSLF("{\n");
70
0
    if (!planar) {
71
0
        GLSLF("vec2 dir = vec2(%d.0, %d.0);\n", d_x, d_y);
72
0
        GLSL(pt *= dir;)
73
0
        GLSL(float fcoord = dot(fract(pos * size - vec2(0.5)), dir);)
74
0
        GLSLF("vec2 base = pos - fcoord * pt - pt * vec2(%d.0);\n", N / 2 - 1);
75
0
    }
76
0
    GLSL(vec4 c;)
77
0
    if (use_ar) {
78
0
        GLSL(vec4 hi = vec4(0.0);)
79
0
        GLSL(vec4 lo = vec4(1.0);)
80
0
    }
81
0
    pass_sample_separated_get_weights(sc, scaler);
82
0
    GLSLF("// scaler samples\n");
83
0
    for (int n = 0; n < N; n++) {
84
0
        if (planar) {
85
0
            GLSLF("c = texture(texture%d, texcoord%d);\n", n, n);
86
0
        } else {
87
0
            GLSLF("c = texture(tex, base + pt * vec2(%d.0));\n", n);
88
0
        }
89
0
        GLSLF("color += vec4(weights[%d]) * c;\n", n);
90
0
        if (use_ar && (n == N/2-1 || n == N/2)) {
91
0
            GLSL(lo = min(lo, c);)
92
0
            GLSL(hi = max(hi, c);)
93
0
        }
94
0
    }
95
0
    if (use_ar)
96
0
        GLSLF("color = mix(color, clamp(color, lo, hi), %f);\n",
97
0
              scaler->conf.antiring);
98
0
    GLSLF("}\n");
99
0
}
100
101
// Subroutine for computing and adding an individual texel contribution
102
// If planar is false, samples directly
103
// If planar is true, takes the pixel from inX[idx] where X is the component and
104
// `idx` must be defined by the caller
105
static void polar_sample(struct gl_shader_cache *sc, struct scaler *scaler,
106
                         int x, int y, int components, bool planar)
107
0
{
108
0
    double radius = scaler->kernel->radius * scaler->kernel->filter_scale;
109
0
    double radius_cutoff = scaler->kernel->radius_cutoff;
110
111
    // Since we can't know the subpixel position in advance, assume a
112
    // worst case scenario
113
0
    int yy = y > 0 ? y-1 : y;
114
0
    int xx = x > 0 ? x-1 : x;
115
0
    double dmax = sqrt(xx*xx + yy*yy);
116
    // Skip samples definitely outside the radius
117
0
    if (dmax >= radius_cutoff)
118
0
        return;
119
0
    GLSLF("d = length(vec2(%d.0, %d.0) - fcoord);\n", x, y);
120
    // Check for samples that might be skippable
121
0
    bool maybe_skippable = dmax >= radius_cutoff - M_SQRT2;
122
0
    if (maybe_skippable)
123
0
        GLSLF("if (d < %f) {\n", radius_cutoff);
124
125
    // get the weight for this pixel
126
0
    if (scaler->lut->params.dimensions == 1) {
127
0
        GLSLF("w = tex1D(lut, LUT_POS(d * 1.0/%f, %d.0)).r;\n",
128
0
              radius, scaler->lut->params.w);
129
0
    } else {
130
0
        GLSLF("w = texture(lut, vec2(0.5, LUT_POS(d * 1.0/%f, %d.0))).r;\n",
131
0
              radius, scaler->lut->params.h);
132
0
    }
133
0
    GLSL(wsum += w;)
134
135
0
    if (planar) {
136
0
        for (int n = 0; n < components; n++)
137
0
            GLSLF("color[%d] += w * in%d[idx];\n", n, n);
138
0
    } else {
139
0
        GLSLF("in0 = texture(tex, base + pt * vec2(%d.0, %d.0));\n", x, y);
140
0
        GLSL(color += vec4(w) * in0;)
141
0
    }
142
143
0
    if (maybe_skippable)
144
0
        GLSLF("}\n");
145
0
}
146
147
void pass_sample_polar(struct gl_shader_cache *sc, struct scaler *scaler,
148
                       int components, bool sup_gather)
149
0
{
150
0
    GLSL(color = vec4(0.0);)
151
0
    GLSLF("{\n");
152
0
    GLSL(vec2 fcoord = fract(pos * size - vec2(0.5));)
153
0
    GLSL(vec2 base = pos - fcoord * pt;)
154
0
    GLSLF("float w, d, wsum = 0.0;\n");
155
0
    for (int n = 0; n < components; n++)
156
0
        GLSLF("vec4 in%d;\n", n);
157
0
    GLSL(int idx;)
158
159
0
    gl_sc_uniform_texture(sc, "lut", scaler->lut);
160
161
0
    GLSLF("// scaler samples\n");
162
0
    int bound = ceil(scaler->kernel->radius_cutoff);
163
0
    for (int y = 1-bound; y <= bound; y += 2) {
164
0
        for (int x = 1-bound; x <= bound; x += 2) {
165
            // First we figure out whether it's more efficient to use direct
166
            // sampling or gathering. The problem is that gathering 4 texels
167
            // only to discard some of them is very wasteful, so only do it if
168
            // we suspect it will be a win rather than a loss. This is the case
169
            // exactly when all four texels are within bounds
170
0
            bool use_gather = sqrt(x*x + y*y) < scaler->kernel->radius_cutoff;
171
172
0
            if (!sup_gather)
173
0
                use_gather = false;
174
175
0
            if (use_gather) {
176
                // Gather the four surrounding texels simultaneously
177
0
                for (int n = 0; n < components; n++) {
178
0
                    GLSLF("in%d = textureGatherOffset(tex, base, "
179
0
                          "ivec2(%d, %d), %d);\n", n, x, y, n);
180
0
                }
181
182
                // Mix in all of the points with their weights
183
0
                for (int p = 0; p < 4; p++) {
184
                    // The four texels are gathered counterclockwise starting
185
                    // from the bottom left
186
0
                    static const int xo[4] = {0, 1, 1, 0};
187
0
                    static const int yo[4] = {1, 1, 0, 0};
188
0
                    if (x+xo[p] > bound || y+yo[p] > bound)
189
0
                        continue;
190
0
                    GLSLF("idx = %d;\n", p);
191
0
                    polar_sample(sc, scaler, x+xo[p], y+yo[p], components, true);
192
0
                }
193
0
            } else {
194
                // switch to direct sampling instead, for efficiency/compatibility
195
0
                for (int yy = y; yy <= bound && yy <= y+1; yy++) {
196
0
                    for (int xx = x; xx <= bound && xx <= x+1; xx++)
197
0
                        polar_sample(sc, scaler, xx, yy, components, false);
198
0
                }
199
0
            }
200
0
        }
201
0
    }
202
203
0
    GLSL(color = color / vec4(wsum);)
204
0
    GLSLF("}\n");
205
0
}
206
207
// bw/bh: block size
208
// iw/ih: input size (pre-calculated to fit all required texels)
209
void pass_compute_polar(struct gl_shader_cache *sc, struct scaler *scaler,
210
                        int components, int bw, int bh, int iw, int ih)
211
0
{
212
0
    int bound = ceil(scaler->kernel->radius_cutoff);
213
0
    int offset = bound - 1; // padding top/left
214
215
0
    GLSL(color = vec4(0.0);)
216
0
    GLSLF("{\n");
217
0
    GLSL(vec2 wpos = texmap(gl_WorkGroupID * gl_WorkGroupSize);)
218
0
    GLSL(vec2 wbase = wpos - pt * fract(wpos * size - vec2(0.5));)
219
0
    GLSL(vec2 fcoord = fract(pos * size - vec2(0.5));)
220
0
    GLSL(vec2 base = pos - pt * fcoord;)
221
0
    GLSL(ivec2 rel = ivec2(round((base - wbase) * size));)
222
0
    GLSL(int idx;)
223
0
    GLSLF("float w, d, wsum = 0.0;\n");
224
0
    gl_sc_uniform_texture(sc, "lut", scaler->lut);
225
226
    // Load all relevant texels into shmem
227
0
    for (int c = 0; c < components; c++)
228
0
        GLSLHF("shared float in%d[%d];\n", c, ih * iw);
229
230
0
    GLSL(vec4 c;)
231
0
    GLSLF("for (int y = int(gl_LocalInvocationID.y); y < %d; y += %d) {\n", ih, bh);
232
0
    GLSLF("for (int x = int(gl_LocalInvocationID.x); x < %d; x += %d) {\n", iw, bw);
233
0
    GLSLF("c = texture(tex, wbase + pt * vec2(x - %d, y - %d));\n", offset, offset);
234
0
    for (int c = 0; c < components; c++)
235
0
        GLSLF("in%d[%d * y + x] = c[%d];\n", c, iw, c);
236
0
    GLSLF("}}\n");
237
0
    GLSL(groupMemoryBarrier();)
238
0
    GLSL(barrier();)
239
240
    // Dispatch the actual samples
241
0
    GLSLF("// scaler samples\n");
242
0
    for (int y = 1-bound; y <= bound; y++) {
243
0
        for (int x = 1-bound; x <= bound; x++) {
244
0
            GLSLF("idx = %d * rel.y + rel.x + %d;\n", iw,
245
0
                  iw * (y + offset) + x + offset);
246
0
            polar_sample(sc, scaler, x, y, components, true);
247
0
        }
248
0
    }
249
250
0
    GLSL(color = color / vec4(wsum);)
251
0
    GLSLF("}\n");
252
0
}
253
254
static void bicubic_calcweights(struct gl_shader_cache *sc, const char *t, const char *s)
255
0
{
256
    // Explanation of how bicubic scaling with only 4 texel fetches is done:
257
    //   <https://web.archive.org/web/20180720154854/http://www.mate.tue.nl/mate/pdfs/10318.pdf>
258
    //   'Efficient GPU-Based Texture Interpolation using Uniform B-Splines'
259
    // Explanation why this algorithm normally always blurs, even with unit
260
    // scaling:
261
    //   http://bigwww.epfl.ch/preprints/ruijters1001p.pdf
262
    //   'GPU Prefilter for Accurate Cubic B-spline Interpolation'
263
0
    GLSLF("vec4 %s = vec4(-0.5, 0.1666, 0.3333, -0.3333) * %s"
264
0
                " + vec4(1, 0, -0.5, 0.5);\n", t, s);
265
0
    GLSLF("%s = %s * %s + vec4(0, 0, -0.5, 0.5);\n", t, t, s);
266
0
    GLSLF("%s = %s * %s + vec4(-0.6666, 0, 0.8333, 0.1666);\n", t, t, s);
267
0
    GLSLF("%s.xy *= vec2(1, 1) / vec2(%s.z, %s.w);\n", t, t, t);
268
0
    GLSLF("%s.xy += vec2(1.0 + %s, 1.0 - %s);\n", t, s, s);
269
0
}
270
271
void pass_sample_bicubic_fast(struct gl_shader_cache *sc)
272
0
{
273
0
    GLSLF("{\n");
274
0
    GLSL(vec2 fcoord = fract(pos * size + vec2(0.5, 0.5));)
275
0
    bicubic_calcweights(sc, "parmx", "fcoord.x");
276
0
    bicubic_calcweights(sc, "parmy", "fcoord.y");
277
0
    GLSL(vec4 cdelta;)
278
0
    GLSL(cdelta.xz = parmx.rg * vec2(-pt.x, pt.x);)
279
0
    GLSL(cdelta.yw = parmy.rg * vec2(-pt.y, pt.y);)
280
    // first y-interpolation
281
0
    GLSL(vec4 ar = texture(tex, pos + cdelta.xy);)
282
0
    GLSL(vec4 ag = texture(tex, pos + cdelta.xw);)
283
0
    GLSL(vec4 ab = mix(ag, ar, parmy.b);)
284
    // second y-interpolation
285
0
    GLSL(vec4 br = texture(tex, pos + cdelta.zy);)
286
0
    GLSL(vec4 bg = texture(tex, pos + cdelta.zw);)
287
0
    GLSL(vec4 aa = mix(bg, br, parmy.b);)
288
    // x-interpolation
289
0
    GLSL(color = mix(aa, ab, parmx.b);)
290
0
    GLSLF("}\n");
291
0
}
292
293
void pass_sample_oversample(struct gl_shader_cache *sc, struct scaler *scaler,
294
                                   int w, int h)
295
0
{
296
0
    GLSLF("{\n");
297
0
    GLSL(vec2 pos = pos - vec2(0.5) * pt;) // round to nearest
298
0
    GLSL(vec2 fcoord = fract(pos * size - vec2(0.5));)
299
    // Determine the mixing coefficient vector
300
0
    gl_sc_uniform_vec2(sc, "output_size", (float[2]){w, h});
301
0
    GLSL(vec2 coeff = fcoord * output_size/size;)
302
0
    float threshold = scaler->conf.kernel.params[0];
303
0
    threshold = isnan(threshold) ? 0.0 : threshold;
304
0
    GLSLF("coeff = (coeff - %f) * 1.0/%f;\n", threshold, 1.0 - 2 * threshold);
305
0
    GLSL(coeff = clamp(coeff, 0.0, 1.0);)
306
    // Compute the right blend of colors
307
0
    GLSL(color = texture(tex, pos + pt * (coeff - fcoord));)
308
0
    GLSLF("}\n");
309
0
}
310
311
// Common constants for SMPTE ST.2084 (HDR)
312
static const float PQ_M1 = 2610./4096 * 1./4,
313
                   PQ_M2 = 2523./4096 * 128,
314
                   PQ_C1 = 3424./4096,
315
                   PQ_C2 = 2413./4096 * 32,
316
                   PQ_C3 = 2392./4096 * 32;
317
318
// Common constants for ARIB STD-B67 (HLG)
319
static const float HLG_A = 0.17883277,
320
                   HLG_B = 0.28466892,
321
                   HLG_C = 0.55991073;
322
323
// Common constants for Panasonic V-Log
324
static const float VLOG_B = 0.00873,
325
                   VLOG_C = 0.241514,
326
                   VLOG_D = 0.598206;
327
328
// Common constants for Sony S-Log
329
static const float SLOG_A = 0.432699,
330
                   SLOG_B = 0.037584,
331
                   SLOG_C = 0.616596 + 0.03,
332
                   SLOG_P = 3.538813,
333
                   SLOG_Q = 0.030001,
334
                   SLOG_K2 = 155.0 / 219.0;
335
336
// Linearize (expand), given a TRC as input. In essence, this is the ITU-R
337
// EOTF, calculated on an idealized (reference) monitor with a white point of
338
// MP_REF_WHITE and infinite contrast.
339
//
340
// These functions always output to a normalized scale of [0,1], for
341
// convenience of the video.c code that calls it. To get the values in an
342
// absolute scale, multiply the result by `pl_color_transfer_nominal_peak(trc)`
343
void pass_linearize(struct gl_shader_cache *sc, enum pl_color_transfer trc)
344
0
{
345
0
    if (trc == PL_COLOR_TRC_LINEAR || trc == PL_COLOR_TRC_SCRGB)
346
0
        return;
347
348
0
    GLSLF("// linearize\n");
349
350
    // Note that this clamp may technically violate the definition of
351
    // ITU-R BT.2100, which allows for sub-blacks and super-whites to be
352
    // displayed on the display where such would be possible. That said, the
353
    // problem is that not all gamma curves are well-defined on the values
354
    // outside this range, so we ignore it and just clip anyway for sanity.
355
0
    GLSL(color.rgb = clamp(color.rgb, 0.0, 1.0);)
356
357
0
    switch (trc) {
358
0
    case PL_COLOR_TRC_SRGB:
359
0
        GLSLF("color.rgb = mix(color.rgb * vec3(1.0/12.92),             \n"
360
0
              "                pow((color.rgb + vec3(0.055))/vec3(1.055), vec3(2.4)), \n"
361
0
              "                %s(lessThan(vec3(0.04045), color.rgb))); \n",
362
0
              gl_sc_bvec(sc, 3));
363
0
        break;
364
0
    case PL_COLOR_TRC_BT_1886:
365
0
        GLSL(color.rgb = pow(color.rgb, vec3(2.4));)
366
0
        break;
367
0
    case PL_COLOR_TRC_GAMMA18:
368
0
        GLSL(color.rgb = pow(color.rgb, vec3(1.8));)
369
0
        break;
370
0
    case PL_COLOR_TRC_GAMMA20:
371
0
        GLSL(color.rgb = pow(color.rgb, vec3(2.0));)
372
0
        break;
373
0
    case PL_COLOR_TRC_GAMMA22:
374
0
        GLSL(color.rgb = pow(color.rgb, vec3(2.2));)
375
0
        break;
376
0
    case PL_COLOR_TRC_GAMMA24:
377
0
        GLSL(color.rgb = pow(color.rgb, vec3(2.4));)
378
0
        break;
379
0
    case PL_COLOR_TRC_GAMMA26:
380
0
        GLSL(color.rgb = pow(color.rgb, vec3(2.6));)
381
0
        break;
382
0
    case PL_COLOR_TRC_GAMMA28:
383
0
        GLSL(color.rgb = pow(color.rgb, vec3(2.8));)
384
0
        break;
385
0
    case PL_COLOR_TRC_PRO_PHOTO:
386
0
        GLSLF("color.rgb = mix(color.rgb * vec3(1.0/16.0),              \n"
387
0
              "                pow(color.rgb, vec3(1.8)),               \n"
388
0
              "                %s(lessThan(vec3(0.03125), color.rgb))); \n",
389
0
              gl_sc_bvec(sc, 3));
390
0
        break;
391
0
    case PL_COLOR_TRC_PQ:
392
0
        GLSLF("color.rgb = pow(color.rgb, vec3(1.0/%f));\n", PQ_M2);
393
0
        GLSLF("color.rgb = max(color.rgb - vec3(%f), vec3(0.0)) \n"
394
0
              "             / (vec3(%f) - vec3(%f) * color.rgb);\n",
395
0
              PQ_C1, PQ_C2, PQ_C3);
396
0
        GLSLF("color.rgb = pow(color.rgb, vec3(%f));\n", 1.0 / PQ_M1);
397
        // PQ's output range is 0-10000, but we need it to be relative to
398
        // MP_REF_WHITE instead, so rescale
399
0
        GLSLF("color.rgb *= vec3(%f);\n", 10000 / MP_REF_WHITE);
400
0
        break;
401
0
    case PL_COLOR_TRC_HLG:
402
0
        GLSLF("color.rgb = mix(vec3(4.0) * color.rgb * color.rgb,\n"
403
0
              "                exp((color.rgb - vec3(%f)) * vec3(1.0/%f)) + vec3(%f),\n"
404
0
              "                %s(lessThan(vec3(0.5), color.rgb)));\n",
405
0
              HLG_C, HLG_A, HLG_B, gl_sc_bvec(sc, 3));
406
0
        GLSLF("color.rgb *= vec3(1.0/%f);\n", MP_REF_WHITE_HLG);
407
0
        break;
408
0
    case PL_COLOR_TRC_V_LOG:
409
0
        GLSLF("color.rgb = mix((color.rgb - vec3(0.125)) * vec3(1.0/5.6), \n"
410
0
              "    pow(vec3(10.0), (color.rgb - vec3(%f)) * vec3(1.0/%f)) \n"
411
0
              "              - vec3(%f),                                  \n"
412
0
              "    %s(lessThanEqual(vec3(0.181), color.rgb)));            \n",
413
0
              VLOG_D, VLOG_C, VLOG_B, gl_sc_bvec(sc, 3));
414
0
        break;
415
0
    case PL_COLOR_TRC_S_LOG1:
416
0
        GLSLF("color.rgb = mix((color.rgb - vec3(%f)) * vec3(1.0/5.0),    \n"
417
0
              "    pow(vec3(10.0), (color.rgb - vec3(%f)) * vec3(1.0/%f)) \n"
418
0
              "        - vec3(%f),                                        \n"
419
0
              "    %s(lessThanEqual(vec3(%f), color.rgb)));               \n",
420
0
              SLOG_Q, SLOG_C, SLOG_A, SLOG_B, gl_sc_bvec(sc, 3), SLOG_Q);
421
0
        break;
422
0
    case PL_COLOR_TRC_S_LOG2:
423
0
        GLSLF("color.rgb = mix((color.rgb - vec3(%f)) * vec3(1.0/%f),      \n"
424
0
              "    (pow(vec3(10.0), (color.rgb - vec3(%f)) * vec3(1.0/%f)) \n"
425
0
              "              - vec3(%f)) * vec3(1.0/%f),                   \n"
426
0
              "    %s(lessThanEqual(vec3(%f), color.rgb)));                \n",
427
0
              SLOG_Q, SLOG_P, SLOG_C, SLOG_A, SLOG_B, SLOG_K2, gl_sc_bvec(sc, 3), SLOG_Q);
428
0
        break;
429
0
    case PL_COLOR_TRC_ST428:
430
0
        GLSL(color.rgb = vec3(52.37/48.0) * pow(color.rgb, vec3(2.6)););
431
0
        break;
432
0
    default:
433
0
        abort();
434
0
    }
435
436
    // Rescale to prevent clipping on non-float textures
437
0
    GLSLF("color.rgb *= vec3(1.0/%f);\n", pl_color_transfer_nominal_peak(trc));
438
0
}
439
440
// Delinearize (compress), given a TRC as output. This corresponds to the
441
// inverse EOTF (not the OETF) in ITU-R terminology, again assuming a
442
// reference monitor.
443
//
444
// Like pass_linearize, this functions ingests values on an normalized scale
445
void pass_delinearize(struct gl_shader_cache *sc, enum pl_color_transfer trc)
446
0
{
447
0
    if (trc == PL_COLOR_TRC_LINEAR || trc == PL_COLOR_TRC_SCRGB)
448
0
        return;
449
450
0
    GLSLF("// delinearize\n");
451
0
    GLSL(color.rgb = clamp(color.rgb, 0.0, 1.0);)
452
0
    GLSLF("color.rgb *= vec3(%f);\n", pl_color_transfer_nominal_peak(trc));
453
454
0
    switch (trc) {
455
0
    case PL_COLOR_TRC_SRGB:
456
0
        GLSLF("color.rgb = mix(color.rgb * vec3(12.92),                       \n"
457
0
              "               vec3(1.055) * pow(color.rgb, vec3(1.0/2.4))     \n"
458
0
              "                   - vec3(0.055),                              \n"
459
0
              "               %s(lessThanEqual(vec3(0.0031308), color.rgb))); \n",
460
0
              gl_sc_bvec(sc, 3));
461
0
        break;
462
0
    case PL_COLOR_TRC_BT_1886:
463
0
        GLSL(color.rgb = pow(color.rgb, vec3(1.0/2.4));)
464
0
        break;
465
0
    case PL_COLOR_TRC_GAMMA18:
466
0
        GLSL(color.rgb = pow(color.rgb, vec3(1.0/1.8));)
467
0
        break;
468
0
    case PL_COLOR_TRC_GAMMA20:
469
0
        GLSL(color.rgb = pow(color.rgb, vec3(1.0/2.0));)
470
0
        break;
471
0
    case PL_COLOR_TRC_GAMMA22:
472
0
        GLSL(color.rgb = pow(color.rgb, vec3(1.0/2.2));)
473
0
        break;
474
0
    case PL_COLOR_TRC_GAMMA24:
475
0
        GLSL(color.rgb = pow(color.rgb, vec3(1.0/2.4));)
476
0
        break;
477
0
    case PL_COLOR_TRC_GAMMA26:
478
0
        GLSL(color.rgb = pow(color.rgb, vec3(1.0/2.6));)
479
0
        break;
480
0
    case PL_COLOR_TRC_GAMMA28:
481
0
        GLSL(color.rgb = pow(color.rgb, vec3(1.0/2.8));)
482
0
        break;
483
0
    case PL_COLOR_TRC_PRO_PHOTO:
484
0
        GLSLF("color.rgb = mix(color.rgb * vec3(16.0),                           \n"
485
0
              "                pow(color.rgb, vec3(1.0/1.8)),                    \n"
486
0
              "                %s(lessThanEqual(vec3(0.001953125), color.rgb))); \n",
487
0
              gl_sc_bvec(sc, 3));
488
0
        break;
489
0
    case PL_COLOR_TRC_PQ:
490
0
        GLSLF("color.rgb *= vec3(1.0/%f);\n", 10000 / MP_REF_WHITE);
491
0
        GLSLF("color.rgb = pow(color.rgb, vec3(%f));\n", PQ_M1);
492
0
        GLSLF("color.rgb = (vec3(%f) + vec3(%f) * color.rgb) \n"
493
0
              "             / (vec3(1.0) + vec3(%f) * color.rgb);\n",
494
0
              PQ_C1, PQ_C2, PQ_C3);
495
0
        GLSLF("color.rgb = pow(color.rgb, vec3(%f));\n", PQ_M2);
496
0
        break;
497
0
    case PL_COLOR_TRC_HLG:
498
0
        GLSLF("color.rgb *= vec3(%f);\n", MP_REF_WHITE_HLG);
499
0
        GLSLF("color.rgb = mix(vec3(0.5) * sqrt(color.rgb),\n"
500
0
              "                vec3(%f) * log(color.rgb - vec3(%f)) + vec3(%f),\n"
501
0
              "                %s(lessThan(vec3(1.0), color.rgb)));\n",
502
0
              HLG_A, HLG_B, HLG_C, gl_sc_bvec(sc, 3));
503
0
        break;
504
0
    case PL_COLOR_TRC_V_LOG:
505
0
        GLSLF("color.rgb = mix(vec3(5.6) * color.rgb + vec3(0.125),   \n"
506
0
              "                vec3(%f) * log(color.rgb + vec3(%f))   \n"
507
0
              "                    + vec3(%f),                        \n"
508
0
              "                %s(lessThanEqual(vec3(0.01), color.rgb))); \n",
509
0
              VLOG_C / M_LN10, VLOG_B, VLOG_D, gl_sc_bvec(sc, 3));
510
0
        break;
511
0
    case PL_COLOR_TRC_S_LOG1:
512
0
        GLSLF("color.rgb = mix(vec3(5.0) * color.rgb + vec3(%f),               \n"
513
0
              "                vec3(%f) * log(color.rgb + vec3(%f)) + vec3(%f), \n"
514
0
              "                %s(lessThanEqual(vec3(0.0), color.rgb)));       \n",
515
0
              SLOG_Q, SLOG_A / M_LN10, SLOG_B, SLOG_C, gl_sc_bvec(sc, 3));
516
0
        break;
517
0
    case PL_COLOR_TRC_S_LOG2:
518
0
        GLSLF("color.rgb = mix(vec3(%f) * color.rgb + vec3(%f),                \n"
519
0
              "                vec3(%f) * log(vec3(%f) * color.rgb + vec3(%f)) \n"
520
0
              "                    + vec3(%f),                                 \n"
521
0
              "                %s(lessThanEqual(vec3(0.0), color.rgb)));       \n",
522
0
              SLOG_P, SLOG_Q, SLOG_A / M_LN10, SLOG_K2, SLOG_B, SLOG_C, gl_sc_bvec(sc, 3));
523
0
        break;
524
0
    case PL_COLOR_TRC_ST428:
525
0
        GLSL(color.rgb = pow(color.rgb * vec3(48.0/52.37), vec3(1.0/2.6)););
526
0
        break;
527
0
    default:
528
0
        abort();
529
0
    }
530
0
}
531
532
// Apply the OOTF mapping from a given light type to display-referred light.
533
// Assumes absolute scale values. `peak` is used to tune the OOTF where
534
// applicable (currently only HLG).
535
static void pass_ootf(struct gl_shader_cache *sc, enum mp_csp_light light,
536
                      float peak)
537
0
{
538
0
    if (light == MP_CSP_LIGHT_DISPLAY)
539
0
        return;
540
541
0
    GLSLF("// apply ootf\n");
542
543
0
    switch (light)
544
0
    {
545
0
    case MP_CSP_LIGHT_SCENE_HLG: {
546
        // HLG OOTF from BT.2100, scaled to the chosen display peak, with the
547
        // extended range system gamma of BT.2100-3 note 5f for every peak.
548
        // The luma is floored because the gamma drops below 1 for dim
549
        // displays, where the power of a zero luma is infinite.
550
0
        float gamma = 1.2 * pow(1.111, log2(peak * MP_REF_WHITE / 1000.0));
551
0
        GLSLF("color.rgb *= vec3(%f * pow(max(dot(src_luma, color.rgb), 1e-6), %f));\n",
552
0
              peak / pow(12.0 / MP_REF_WHITE_HLG, gamma), gamma - 1.0);
553
0
        break;
554
0
    }
555
0
    case MP_CSP_LIGHT_SCENE_709_1886:
556
        // This OOTF is defined by encoding the result as 709 and then decoding
557
        // it as 1886; although this is called 709_1886 we actually use the
558
        // more precise (by one decimal) values from BT.2020 instead
559
0
        GLSLF("color.rgb = mix(color.rgb * vec3(4.5),                  \n"
560
0
              "                vec3(1.0993) * pow(color.rgb, vec3(0.45)) - vec3(0.0993), \n"
561
0
              "                %s(lessThan(vec3(0.0181), color.rgb))); \n",
562
0
              gl_sc_bvec(sc, 3));
563
0
        GLSL(color.rgb = pow(color.rgb, vec3(2.4));)
564
0
        break;
565
0
    case MP_CSP_LIGHT_SCENE_1_2:
566
0
        GLSL(color.rgb = pow(color.rgb, vec3(1.2));)
567
0
        break;
568
0
    default:
569
0
        abort();
570
0
    }
571
0
}
572
573
// Inverse of the function pass_ootf, for completeness' sake.
574
static void pass_inverse_ootf(struct gl_shader_cache *sc, enum mp_csp_light light,
575
                              float peak)
576
0
{
577
0
    if (light == MP_CSP_LIGHT_DISPLAY)
578
0
        return;
579
580
0
    GLSLF("// apply inverse ootf\n");
581
582
0
    switch (light)
583
0
    {
584
0
    case MP_CSP_LIGHT_SCENE_HLG: {
585
0
        float gamma = 1.2 * pow(1.111, log2(peak * MP_REF_WHITE / 1000.0));
586
0
        GLSLF("color.rgb *= vec3(1.0/%f);\n", peak / pow(12.0 / MP_REF_WHITE_HLG, gamma));
587
0
        GLSLF("color.rgb /= vec3(pow(max(dot(src_luma, color.rgb), 1e-6), %f));\n",
588
0
              (gamma - 1.0) / gamma);
589
0
        break;
590
0
    }
591
0
    case MP_CSP_LIGHT_SCENE_709_1886:
592
0
        GLSL(color.rgb = pow(color.rgb, vec3(1.0/2.4));)
593
0
        GLSLF("color.rgb = mix(color.rgb * vec3(1.0/4.5),               \n"
594
0
              "                pow((color.rgb + vec3(0.0993)) * vec3(1.0/1.0993), \n"
595
0
              "                    vec3(1/0.45)),                       \n"
596
0
              "                %s(lessThan(vec3(0.08145), color.rgb))); \n",
597
0
              gl_sc_bvec(sc, 3));
598
0
        break;
599
0
    case MP_CSP_LIGHT_SCENE_1_2:
600
0
        GLSL(color.rgb = pow(color.rgb, vec3(1.0/1.2));)
601
0
        break;
602
0
    default:
603
0
        abort();
604
0
    }
605
0
}
606
607
// Average light level for SDR signals. This is equal to a signal level of 0.5
608
// under a typical presentation gamma of about 2.0.
609
static const float sdr_avg = 0.25;
610
611
static void hdr_update_peak(struct gl_shader_cache *sc,
612
                            const struct gl_tone_map_opts *opts)
613
0
{
614
    // Update the sig_peak/sig_avg from the old SSBO state
615
0
    GLSL(if (average.y > 0.0) {)
616
0
    GLSL(    sig_avg  = max(1e-3, average.x);)
617
0
    GLSL(    sig_peak = max(1.00, average.y);)
618
0
    GLSL(})
619
620
    // Chosen to avoid overflowing on an 8K buffer
621
0
    const float log_min = 1e-3, log_scale = 400.0, sig_scale = 10000.0;
622
623
    // For performance, and to avoid overflows, we tally up the sub-results per
624
    // pixel using shared memory first
625
0
    GLSLH(shared int wg_sum;)
626
0
    GLSLH(shared uint wg_max;)
627
0
    GLSL(wg_sum = 0; wg_max = 0u;)
628
0
    GLSL(barrier();)
629
0
    GLSLF("float sig_log = log(max(sig_max, %f));\n", log_min);
630
0
    GLSLF("atomicAdd(wg_sum, int(sig_log * %f));\n", log_scale);
631
0
    GLSLF("atomicMax(wg_max, uint(sig_max * %f));\n", sig_scale);
632
633
    // Have one thread per work group update the global atomics
634
0
    GLSL(memoryBarrierShared();)
635
0
    GLSL(barrier();)
636
0
    GLSL(if (gl_LocalInvocationIndex == 0u) {)
637
0
    GLSL(    int wg_avg = wg_sum / int(gl_WorkGroupSize.x * gl_WorkGroupSize.y);)
638
0
    GLSL(    atomicAdd(frame_sum, wg_avg);)
639
0
    GLSL(    atomicMax(frame_max, wg_max);)
640
0
    GLSL(    memoryBarrierBuffer();)
641
0
    GLSL(})
642
0
    GLSL(barrier();)
643
644
    // Finally, to update the global state, we increment a counter per dispatch
645
0
    GLSL(uint num_wg = gl_NumWorkGroups.x * gl_NumWorkGroups.y;)
646
0
    GLSL(if (gl_LocalInvocationIndex == 0u && atomicAdd(counter, 1u) == num_wg - 1u) {)
647
0
    GLSL(    counter = 0u;)
648
0
    GLSL(    vec2 cur = vec2(float(frame_sum) / float(num_wg), frame_max);)
649
0
    GLSLF("  cur *= vec2(1.0/%f, 1.0/%f);\n", log_scale, sig_scale);
650
0
    GLSL(    cur.x = exp(cur.x);)
651
0
    GLSL(    if (average.y == 0.0))
652
0
    GLSL(        average = cur;)
653
654
    // Use an IIR low-pass filter to smooth out the detected values, with a
655
    // configurable decay rate based on the desired time constant (tau)
656
0
    if (opts->decay_rate) {
657
0
        float decay = 1.0f - expf(-1.0f / opts->decay_rate);
658
0
        GLSLF("  average += %f * (cur - average);\n", decay);
659
0
    } else {
660
0
        GLSLF("  average = cur;\n");
661
0
    }
662
663
    // Scene change hysteresis
664
0
    float log_db = 10.0 / log(10.0);
665
0
    GLSLF("  float weight = smoothstep(%f, %f, abs(log(cur.x / average.x)));\n",
666
0
          opts->scene_threshold_low / log_db,
667
0
          opts->scene_threshold_high / log_db);
668
0
    GLSL(    average = mix(average, cur, weight);)
669
670
    // Reset SSBO state for the next frame
671
0
    GLSL(    frame_sum = 0; frame_max = 0u;)
672
0
    GLSL(    memoryBarrierBuffer();)
673
0
    GLSL(})
674
0
}
675
676
static inline float pq_delinearize(float x)
677
0
{
678
0
    x *= MP_REF_WHITE / 10000.0;
679
0
    x = powf(x, PQ_M1);
680
0
    x = (PQ_C1 + PQ_C2 * x) / (1.0 + PQ_C3 * x);
681
0
    x = pow(x, PQ_M2);
682
0
    return x;
683
0
}
684
685
// Tone map from a known peak brightness to the range [0,1]. If ref_peak
686
// is 0, we will use peak detection instead
687
static void pass_tone_map(struct gl_shader_cache *sc,
688
                          float src_peak, float dst_peak,
689
                          const struct gl_tone_map_opts *opts)
690
0
{
691
0
    GLSLF("// HDR tone mapping\n");
692
693
    // To prevent discoloration due to out-of-bounds clipping, we need to make
694
    // sure to reduce the value range as far as necessary to keep the entire
695
    // signal in range, so tone map based on the brightest component.
696
0
    GLSL(int sig_idx = 0;)
697
0
    GLSL(if (color[1] > color[sig_idx]) sig_idx = 1;)
698
0
    GLSL(if (color[2] > color[sig_idx]) sig_idx = 2;)
699
0
    GLSL(float sig_max = color[sig_idx];)
700
0
    GLSLF("float sig_peak = %f;\n", src_peak);
701
0
    GLSLF("float sig_avg = %f;\n", sdr_avg);
702
703
0
    if (opts->compute_peak >= 0)
704
0
        hdr_update_peak(sc, opts);
705
706
    // Always hard-clip the upper bound of the signal range to avoid functions
707
    // exploding on inputs greater than 1.0
708
0
    GLSLF("vec3 sig = min(color.rgb, sig_peak);\n");
709
710
    // This function always operates on an absolute scale, so ignore the
711
    // dst_peak normalization for it
712
0
    float dst_scale = dst_peak;
713
0
    enum tone_mapping curve = opts->curve ? opts->curve : TONE_MAPPING_BT_2390;
714
0
    if (curve == TONE_MAPPING_BT_2390)
715
0
        dst_scale = 1.0;
716
717
    // Rescale the variables in order to bring it into a representation where
718
    // 1.0 represents the dst_peak. This is because all of the tone mapping
719
    // algorithms are defined in such a way that they map to the range [0.0, 1.0].
720
0
    if (dst_scale > 1.0) {
721
0
        GLSLF("sig *= 1.0/%f;\n", dst_scale);
722
0
        GLSLF("sig_peak *= 1.0/%f;\n", dst_scale);
723
0
    }
724
725
0
    GLSL(float sig_orig = sig[sig_idx];)
726
0
    GLSLF("float slope = min(%f, %f / sig_avg);\n", opts->max_boost, sdr_avg);
727
0
    GLSL(sig *= slope;)
728
0
    GLSL(sig_peak *= slope;)
729
730
0
    float param = opts->curve_param;
731
0
    switch (curve) {
732
0
    case TONE_MAPPING_CLIP:
733
0
        GLSLF("sig = min(%f * sig, 1.0);\n", isnan(param) ? 1.0 : param);
734
0
        break;
735
736
0
    case TONE_MAPPING_MOBIUS:
737
0
        GLSLF("if (sig_peak > (1.0 + 1e-6)) {\n");
738
0
        GLSLF("const float j = %f;\n", isnan(param) ? 0.3 : param);
739
        // solve for M(j) = j; M(sig_peak) = 1.0; M'(j) = 1.0
740
        // where M(x) = scale * (x+a)/(x+b)
741
0
        GLSLF("float a = -j*j * (sig_peak - 1.0) / (j*j - 2.0*j + sig_peak);\n");
742
0
        GLSLF("float b = (j*j - 2.0*j*sig_peak + sig_peak) / "
743
0
              "max(1e-6, sig_peak - 1.0);\n");
744
0
        GLSLF("float scale = (b*b + 2.0*b*j + j*j) / (b-a);\n");
745
0
        GLSLF("sig = mix(sig, scale * (sig + vec3(a)) / (sig + vec3(b)),"
746
0
              "          %s(greaterThan(sig, vec3(j))));\n",
747
0
              gl_sc_bvec(sc, 3));
748
0
        GLSLF("}\n");
749
0
        break;
750
751
0
    case TONE_MAPPING_REINHARD: {
752
0
        float contrast = isnan(param) ? 0.5 : param,
753
0
              offset = (1.0 - contrast) / contrast;
754
0
        GLSLF("sig = sig / (sig + vec3(%f));\n", offset);
755
0
        GLSLF("float scale = (sig_peak + %f) / sig_peak;\n", offset);
756
0
        GLSL(sig *= scale;)
757
0
        break;
758
0
    }
759
760
0
    case TONE_MAPPING_HABLE: {
761
0
        float A = 0.15, B = 0.50, C = 0.10, D = 0.20, E = 0.02, F = 0.30;
762
0
        GLSLHF("vec3 hable(vec3 x) {\n");
763
0
        GLSLHF("return (x * (%f*x + vec3(%f)) + vec3(%f)) / "
764
0
               "       (x * (%f*x + vec3(%f)) + vec3(%f)) "
765
0
               "       - vec3(%f);\n",
766
0
               A, C*B, D*E,
767
0
               A, B, D*F,
768
0
               E/F);
769
0
        GLSLHF("}\n");
770
0
        GLSLF("sig = hable(max(vec3(0.0), sig)) / hable(vec3(sig_peak)).x;\n");
771
0
        break;
772
0
    }
773
774
0
    case TONE_MAPPING_GAMMA: {
775
0
        float gamma = isnan(param) ? 1.8 : param;
776
0
        GLSLF("const float cutoff = 0.05, gamma = 1.0/%f;\n", gamma);
777
0
        GLSL(float scale = pow(cutoff / sig_peak, gamma.x) / cutoff;)
778
0
        GLSLF("sig = mix(scale * sig,"
779
0
              "          pow(sig / sig_peak, vec3(gamma)),"
780
0
              "          %s(greaterThan(sig, vec3(cutoff))));\n",
781
0
              gl_sc_bvec(sc, 3));
782
0
        break;
783
0
    }
784
785
0
    case TONE_MAPPING_LINEAR: {
786
0
        float coeff = isnan(param) ? 1.0 : param;
787
0
        GLSLF("sig = min(%f / sig_peak, 1.0) * sig;\n", coeff);
788
0
        break;
789
0
    }
790
791
0
    case TONE_MAPPING_BT_2390:
792
        // We first need to encode both sig and sig_peak into PQ space
793
0
        GLSLF("vec4 sig_pq = vec4(sig.rgb, sig_peak);                           \n"
794
0
              "sig_pq *= vec4(1.0/%f);                                          \n"
795
0
              "sig_pq = pow(sig_pq, vec4(%f));                                  \n"
796
0
              "sig_pq = (vec4(%f) + vec4(%f) * sig_pq)                          \n"
797
0
              "          / (vec4(1.0) + vec4(%f) * sig_pq);                     \n"
798
0
              "sig_pq = pow(sig_pq, vec4(%f));                                  \n",
799
0
              10000.0 / MP_REF_WHITE, PQ_M1, PQ_C1, PQ_C2, PQ_C3, PQ_M2);
800
        // Encode both the signal and the target brightness to be relative to
801
        // the source peak brightness, and figure out the target peak in this space
802
0
        GLSLF("float scale = 1.0 / sig_pq.a;                                    \n"
803
0
              "sig_pq.rgb *= vec3(scale);                                       \n"
804
0
              "float maxLum = %f * scale;                                       \n",
805
0
              pq_delinearize(dst_peak));
806
        // Apply piece-wise hermite spline
807
0
        GLSLF("float ks = 1.5 * maxLum - 0.5;                                   \n"
808
0
              "vec3 tb = (sig_pq.rgb - vec3(ks)) / vec3(1.0 - ks);              \n"
809
0
              "vec3 tb2 = tb * tb;                                              \n"
810
0
              "vec3 tb3 = tb2 * tb;                                             \n"
811
0
              "vec3 pb = (2.0 * tb3 - 3.0 * tb2 + vec3(1.0)) * vec3(ks) +       \n"
812
0
              "          (tb3 - 2.0 * tb2 + tb) * vec3(1.0 - ks) +              \n"
813
0
              "          (-2.0 * tb3 + 3.0 * tb2) * vec3(maxLum);               \n"
814
0
              "sig = mix(pb, sig_pq.rgb, %s(lessThan(sig_pq.rgb, vec3(ks))));   \n",
815
0
              gl_sc_bvec(sc, 3));
816
        // Convert back from PQ space to linear light
817
0
        GLSLF("sig *= vec3(sig_pq.a);                                           \n"
818
0
              "sig = pow(sig, vec3(1.0/%f));                                    \n"
819
0
              "sig = max(sig - vec3(%f), 0.0) /                                 \n"
820
0
              "          (vec3(%f) - vec3(%f) * sig);                           \n"
821
0
              "sig = pow(sig, vec3(1.0/%f));                                    \n"
822
0
              "sig *= vec3(%f);                                                 \n",
823
0
              PQ_M2, PQ_C1, PQ_C2, PQ_C3, PQ_M1, 10000.0 / MP_REF_WHITE);
824
0
        break;
825
826
0
    default:
827
0
        abort();
828
0
    }
829
830
0
    GLSLF("float coeff = max(sig[sig_idx] - %f, 1e-6) / \n"
831
0
          "              max(sig[sig_idx], 1.0);        \n"
832
0
          "coeff = %f * pow(coeff / %f, %f);            \n"
833
0
          "color.rgb *= sig[sig_idx] / sig_orig;        \n"
834
0
          "color.rgb = mix(color.rgb, %f * sig, coeff); \n",
835
0
          0.18 / dst_scale, 0.90, dst_scale, 0.20, dst_scale);
836
0
}
837
838
// Map colors from one source space to another. These source spaces must be
839
// known (i.e. not MP_CSP_*_AUTO), as this function won't perform any
840
// auto-guessing. If is_linear is true, we assume the input has already been
841
// linearized (e.g. for linear-scaling). If `opts->compute_peak` is true, we
842
// will detect the peak instead of relying on metadata. Note that this requires
843
// the caller to have already bound the appropriate SSBO and set up the compute
844
// shader metadata
845
void pass_color_map(struct gl_shader_cache *sc, bool is_linear,
846
                    const struct pl_color_space *src, const struct pl_color_space *dst,
847
                    enum mp_csp_light src_light, enum mp_csp_light dst_light,
848
                    const struct gl_tone_map_opts *opts)
849
0
{
850
0
    GLSLF("// color mapping\n");
851
852
    // Some operations need access to the video's luma coefficients, so make
853
    // them available
854
0
    pl_matrix3x3 rgb2xyz = pl_get_rgb2xyz_matrix(pl_raw_primaries_get(src->primaries));
855
0
    gl_sc_uniform_vec3(sc, "src_luma", rgb2xyz.m[1]);
856
0
    rgb2xyz = pl_get_rgb2xyz_matrix(pl_raw_primaries_get(dst->primaries));
857
0
    gl_sc_uniform_vec3(sc, "dst_luma", rgb2xyz.m[1]);
858
859
0
    bool need_ootf = src_light != dst_light;
860
0
    if (src_light == MP_CSP_LIGHT_SCENE_HLG && src->hdr.max_luma != dst->hdr.max_luma)
861
0
        need_ootf = true;
862
863
    // All operations from here on require linear light as a starting point,
864
    // so we linearize even if src->gamma == dst->transfer when one of the other
865
    // operations needs it
866
0
    bool need_linear = src->transfer != dst->transfer ||
867
0
                       src->primaries != dst->primaries ||
868
0
                       src->hdr.max_luma != dst->hdr.max_luma ||
869
0
                       need_ootf;
870
871
0
    if (need_linear && !is_linear) {
872
        // We also pull it up so that 1.0 is the reference white
873
0
        pass_linearize(sc, src->transfer);
874
0
        is_linear = true;
875
0
    }
876
877
    // Pre-scale the incoming values into an absolute scale
878
0
    GLSLF("color.rgb *= vec3(%f);\n", pl_color_transfer_nominal_peak(src->transfer));
879
880
0
    if (need_ootf)
881
0
        pass_ootf(sc, src_light, src->hdr.max_luma / MP_REF_WHITE);
882
883
    // Tone map to prevent clipping due to excessive brightness
884
0
    if (src->hdr.max_luma > dst->hdr.max_luma) {
885
0
        pass_tone_map(sc, src->hdr.max_luma / MP_REF_WHITE,
886
0
                      dst->hdr.max_luma / MP_REF_WHITE, opts);
887
0
    }
888
889
    // Adapt to the right colorspace if necessary
890
0
    if (src->primaries != dst->primaries) {
891
0
        const struct pl_raw_primaries *csp_src = pl_raw_primaries_get(src->primaries),
892
0
                                      *csp_dst = pl_raw_primaries_get(dst->primaries);
893
0
        pl_matrix3x3 m = pl_get_color_mapping_matrix(csp_src, csp_dst,
894
0
                                                     PL_INTENT_RELATIVE_COLORIMETRIC);
895
0
        gl_sc_uniform_mat3(sc, "cms_matrix", true, &m.m[0][0]);
896
0
        GLSL(color.rgb = cms_matrix * color.rgb;)
897
898
0
        if (!opts->gamut_mode || opts->gamut_mode == GAMUT_DESATURATE) {
899
0
            GLSL(float cmin = min(min(color.r, color.g), color.b);)
900
0
            GLSL(if (cmin < 0.0) {
901
0
                     float luma = dot(dst_luma, color.rgb);
902
0
                     float coeff = cmin / (cmin - luma);
903
0
                     color.rgb = mix(color.rgb, vec3(luma), coeff);
904
0
                 })
905
0
            GLSLF("float cmax = 1.0/%f * max(max(color.r, color.g), color.b);\n",
906
0
                  dst->hdr.max_luma / MP_REF_WHITE);
907
0
            GLSL(if (cmax > 1.0) color.rgb /= cmax;)
908
0
        }
909
0
    }
910
911
0
    if (need_ootf)
912
0
        pass_inverse_ootf(sc, dst_light, dst->hdr.max_luma / MP_REF_WHITE);
913
914
    // Post-scale the outgoing values from absolute scale to normalized.
915
    // For SDR, we normalize to the chosen signal peak. For HDR, we normalize
916
    // to the encoding range of the transfer function.
917
0
    float dst_range = dst->hdr.max_luma / MP_REF_WHITE;
918
0
    if (pl_color_space_is_hdr(dst))
919
0
        dst_range = pl_color_transfer_nominal_peak(dst->transfer);
920
921
0
    GLSLF("color.rgb *= vec3(%f);\n", 1.0 / dst_range);
922
923
    // Warn for remaining out-of-gamut colors if enabled
924
0
    if (opts->gamut_mode == GAMUT_WARN) {
925
0
        GLSL(if (any(greaterThan(color.rgb, vec3(1.005))) ||
926
0
                 any(lessThan(color.rgb, vec3(-0.005)))))
927
0
            GLSL(color.rgb = vec3(1.0) - color.rgb;) // invert
928
0
    }
929
930
0
    if (is_linear)
931
0
        pass_delinearize(sc, dst->transfer);
932
0
}
933
934
// Wide usage friendly PRNG, shamelessly stolen from a GLSL tricks forum post.
935
// Obtain random numbers by calling rand(h), followed by h = permute(h) to
936
// update the state. Assumes the texture was hooked.
937
// permute() was modified from the original to avoid "large" numbers in
938
// calculations, since low-end mobile GPUs choke on them (overflow).
939
static void prng_init(struct gl_shader_cache *sc, AVLFG *lfg)
940
0
{
941
0
    GLSLH(float mod289(float x)  { return x - floor(x * 1.0/289.0) * 289.0; })
942
0
    GLSLHF("float permute(float x) {\n");
943
0
        GLSLH(return mod289( mod289(34.0*x + 1.0) * (fract(x) + 1.0) );)
944
0
    GLSLHF("}\n");
945
0
    GLSLH(float rand(float x)    { return fract(x * 1.0/41.0); })
946
947
    // Initialize the PRNG by hashing the position + a random uniform
948
0
    GLSL(vec3 _m = vec3(HOOKED_pos, random) + vec3(1.0);)
949
0
    GLSL(float h = permute(permute(permute(_m.x)+_m.y)+_m.z);)
950
0
    gl_sc_uniform_dynamic(sc);
951
0
    gl_sc_uniform_f(sc, "random", (double)av_lfg_get(lfg) / UINT32_MAX);
952
0
}
953
954
const struct deband_opts deband_opts_def = {
955
    .iterations = 1,
956
    .threshold = 48.0,
957
    .range = 16.0,
958
    .grain = 32.0,
959
};
960
961
#define OPT_BASE_STRUCT struct deband_opts
962
const struct m_sub_options deband_conf = {
963
    .opts = (const m_option_t[]) {
964
        {"iterations", OPT_INT(iterations), M_RANGE(0, 16)},
965
        {"threshold", OPT_FLOAT(threshold), M_RANGE(0.0, 4096.0)},
966
        {"range", OPT_FLOAT(range), M_RANGE(1.0, 64.0)},
967
        {"grain", OPT_FLOAT(grain), M_RANGE(0.0, 4096.0)},
968
        {0}
969
    },
970
    .size = sizeof(struct deband_opts),
971
    .defaults = &deband_opts_def,
972
};
973
974
// Stochastically sample a debanded result from a hooked texture.
975
void pass_sample_deband(struct gl_shader_cache *sc, struct deband_opts *opts,
976
                        AVLFG *lfg, enum pl_color_transfer trc)
977
0
{
978
    // Initialize the PRNG
979
0
    GLSLF("{\n");
980
0
    prng_init(sc, lfg);
981
982
    // Helper: Compute a stochastic approximation of the avg color around a
983
    // pixel
984
0
    GLSLHF("vec4 average(float range, inout float h) {\n");
985
        // Compute a random rangle and distance
986
0
        GLSLH(float dist = rand(h) * range;     h = permute(h);)
987
0
        GLSLH(float dir  = rand(h) * 6.2831853; h = permute(h);)
988
0
        GLSLH(vec2 o = dist * vec2(cos(dir), sin(dir));)
989
990
        // Sample at quarter-turn intervals around the source pixel
991
0
        GLSLH(vec4 ref[4];)
992
0
        GLSLH(ref[0] = HOOKED_texOff(vec2( o.x,  o.y));)
993
0
        GLSLH(ref[1] = HOOKED_texOff(vec2(-o.y,  o.x));)
994
0
        GLSLH(ref[2] = HOOKED_texOff(vec2(-o.x, -o.y));)
995
0
        GLSLH(ref[3] = HOOKED_texOff(vec2( o.y, -o.x));)
996
997
        // Return the (normalized) average
998
0
        GLSLH(return (ref[0] + ref[1] + ref[2] + ref[3])*0.25;)
999
0
    GLSLHF("}\n");
1000
1001
    // Sample the source pixel
1002
0
    GLSL(color = HOOKED_tex(HOOKED_pos);)
1003
0
    GLSLF("vec4 avg, diff;\n");
1004
0
    for (int i = 1; i <= opts->iterations; i++) {
1005
        // Sample the average pixel and use it instead of the original if
1006
        // the difference is below the given threshold
1007
0
        GLSLF("avg = average(%f, h);\n", i * opts->range);
1008
0
        GLSL(diff = abs(color - avg);)
1009
0
        GLSLF("color = mix(avg, color, %s(greaterThan(diff, vec4(%f))));\n",
1010
0
              gl_sc_bvec(sc, 4), opts->threshold / (i * 16384.0));
1011
0
    }
1012
1013
    // Add some random noise to smooth out residual differences
1014
0
    GLSL(vec3 noise;)
1015
0
    GLSL(noise.x = rand(h); h = permute(h);)
1016
0
    GLSL(noise.y = rand(h); h = permute(h);)
1017
0
    GLSL(noise.z = rand(h); h = permute(h);)
1018
1019
    // Noise is scaled to the signal level to prevent extreme noise for HDR
1020
0
    float gain = opts->grain/8192.0 / pl_color_transfer_nominal_peak(trc);
1021
0
    GLSLF("color.xyz += %f * (noise - vec3(0.5));\n", gain);
1022
0
    GLSLF("}\n");
1023
0
}
1024
1025
// Assumes the texture was hooked
1026
0
void pass_sample_unsharp(struct gl_shader_cache *sc, float param) {
1027
0
    GLSLF("{\n");
1028
0
    GLSL(float st1 = 1.2;)
1029
0
    GLSL(vec4 p = HOOKED_tex(HOOKED_pos);)
1030
0
    GLSL(vec4 sum1 = HOOKED_texOff(st1 * vec2(+1, +1))
1031
0
                   + HOOKED_texOff(st1 * vec2(+1, -1))
1032
0
                   + HOOKED_texOff(st1 * vec2(-1, +1))
1033
0
                   + HOOKED_texOff(st1 * vec2(-1, -1));)
1034
0
    GLSL(float st2 = 1.5;)
1035
0
    GLSL(vec4 sum2 = HOOKED_texOff(st2 * vec2(+1,  0))
1036
0
                   + HOOKED_texOff(st2 * vec2( 0, +1))
1037
0
                   + HOOKED_texOff(st2 * vec2(-1,  0))
1038
0
                   + HOOKED_texOff(st2 * vec2( 0, -1));)
1039
0
    GLSL(vec4 t = p * 0.859375 + sum2 * -0.1171875 + sum1 * -0.09765625;)
1040
0
    GLSLF("color = p + t * %f;\n", param);
1041
0
    GLSLF("}\n");
1042
0
}