/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 | } |