1 /*
2  * This file is part of libplacebo.
3  *
4  * libplacebo is free software; you can redistribute it and/or
5  * modify it under the terms of the GNU Lesser General Public
6  * License as published by the Free Software Foundation; either
7  * version 2.1 of the License, or (at your option) any later version.
8  *
9  * libplacebo is distributed in the hope that it will be useful,
10  * but WITHOUT ANY WARRANTY; without even the implied warranty of
11  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
12  * GNU Lesser General Public License for more details.
13  *
14  * You should have received a copy of the GNU Lesser General Public
15  * License along with libplacebo. If not, see <http://www.gnu.org/licenses/>.
16  */
17 
18 #include <math.h>
19 #include <ctype.h>
20 
21 #include "shaders.h"
22 
isnumeric(char c)23 static inline bool isnumeric(char c)
24 {
25     return (c >= '0' && c <= '9') || c == '-';
26 }
27 
pl_lut_free(struct pl_custom_lut ** lut)28 void pl_lut_free(struct pl_custom_lut **lut)
29 {
30     pl_free_ptr(lut);
31 }
32 
pl_lut_parse_cube(pl_log log,const char * cstr,size_t cstr_len)33 struct pl_custom_lut *pl_lut_parse_cube(pl_log log, const char *cstr, size_t cstr_len)
34 {
35     struct pl_custom_lut *lut = pl_zalloc_ptr(NULL, lut);
36     pl_str str = (pl_str) { (uint8_t *) cstr, cstr_len };
37     lut->signature = pl_str_hash(str);
38     int entries = 0;
39 
40     float min[3] = { 0.0, 0.0, 0.0 };
41     float max[3] = { 1.0, 1.0, 1.0 };
42 
43     // Parse header
44     while (str.len && !isnumeric(str.buf[0])) {
45         pl_str line = pl_str_strip(pl_str_getline(str, &str));
46         if (!line.len)
47             continue; // skip empty line
48 
49         if (pl_str_eatstart0(&line, "TITLE")) {
50             pl_info(log, "Loading LUT: %.*s", PL_STR_FMT(pl_str_strip(line)));
51             continue;
52         }
53 
54         if (pl_str_eatstart0(&line, "LUT_3D_SIZE")) {
55             line = pl_str_strip(line);
56             int size;
57             if (!pl_str_parse_int(line, &size)) {
58                 pl_err(log, "Failed parsing dimension '%.*s'", PL_STR_FMT(line));
59                 goto error;
60             }
61             if (size <= 0 || size > 1024) {
62                 pl_err(log, "Invalid 3DLUT size: %dx%d%x", size, size, size);
63                 goto error;
64             }
65 
66             lut->size[0] = lut->size[1] = lut->size[2] = size;
67             entries = size * size * size;
68             continue;
69         }
70 
71         if (pl_str_eatstart0(&line, "LUT_1D_SIZE")) {
72             line = pl_str_strip(line);
73             int size;
74             if (!pl_str_parse_int(line, &size)) {
75                 pl_err(log, "Failed parsing dimension '%.*s'", PL_STR_FMT(line));
76                 goto error;
77             }
78             if (size <= 0 || size > 65536) {
79                 pl_err(log, "Invalid 1DLUT size: %d", size);
80                 goto error;
81             }
82 
83             lut->size[0] = size;
84             lut->size[1] = lut->size[2] = 0;
85             entries = size;
86             continue;
87         }
88 
89         if (pl_str_eatstart0(&line, "DOMAIN_MIN")) {
90             line = pl_str_strip(line);
91             if (!pl_str_parse_float(pl_str_split_char(line, ' ', &line), &min[0]) ||
92                 !pl_str_parse_float(pl_str_split_char(line, ' ', &line), &min[1]) ||
93                 !pl_str_parse_float(line, &min[2]))
94             {
95                 pl_err(log, "Failed parsing domain: '%.*s'", PL_STR_FMT(line));
96                 goto error;
97             }
98             continue;
99         }
100 
101         if (pl_str_eatstart0(&line, "DOMAIN_MAX")) {
102             line = pl_str_strip(line);
103             if (!pl_str_parse_float(pl_str_split_char(line, ' ', &line), &max[0]) ||
104                 !pl_str_parse_float(pl_str_split_char(line, ' ', &line), &max[1]) ||
105                 !pl_str_parse_float(line, &max[2]))
106             {
107                 pl_err(log, "Failed parsing domain: '%.*s'", PL_STR_FMT(line));
108                 goto error;
109             }
110             continue;
111         }
112 
113         if (pl_str_eatstart0(&line, "#")) {
114             pl_debug(log, "Unhandled .cube comment: %.*s",
115                      PL_STR_FMT(pl_str_strip(line)));
116             continue;
117         }
118 
119         pl_warn(log, "Unhandled .cube line: %.*s", PL_STR_FMT(pl_str_strip(line)));
120     }
121 
122     if (!entries) {
123         pl_err(log, "Missing LUT size specification?");
124         goto error;
125     }
126 
127     for (int i = 0; i < 3; i++) {
128         if (max[i] - min[i] < 1e-6) {
129             pl_err(log, "Invalid domain range: [%f, %f]", min[i], max[i]);
130             goto error;
131         }
132     }
133 
134     float *data = pl_alloc(lut, sizeof(float[3]) * entries);
135     lut->data = data;
136 
137     // Parse LUT body
138     for (int n = 0; n < entries; n++) {
139         for (int c = 0; c < 3; c++) {
140             static const char * const digits = "0123456789.-";
141 
142             // Extract valid digit sequence
143             size_t len = pl_strspn(str, digits);
144             pl_str entry = (pl_str) { str.buf, len };
145             str.buf += len;
146             str.len -= len;
147 
148             if (!entry.len) {
149                 pl_err(log, "Missing LUT entries? Expected %d, got %d",
150                        entries * 3, n * 3 + c + 1);
151                 goto error;
152             }
153 
154             float num;
155             if (!pl_str_parse_float(entry, &num)) {
156                 pl_err(log, "Failed parsing float value '%.*s'", PL_STR_FMT(entry));
157                 goto error;
158             }
159 
160             // Rescale to range 0.0 - 1.0
161             *data++ = (num - min[c]) / (max[c] - min[c]);
162 
163             // Skip invalid trailing characters
164             size_t sep = pl_strcspn(str, digits);
165             str.buf += sep;
166             str.len -= sep;
167         }
168     }
169 
170     str = pl_str_strip(str);
171     if (str.len)
172         pl_warn(log, "Extra data after LUT?... ignoring");
173 
174     return lut;
175 
176 error:
177     pl_free(lut);
178     return NULL;
179 }
180 
fill_lut(void * datap,const struct sh_lut_params * params)181 static void fill_lut(void *datap, const struct sh_lut_params *params)
182 {
183     const struct pl_custom_lut *lut = params->priv;
184 
185     int dim_r = params->width;
186     int dim_g = PL_DEF(params->height, 1);
187     int dim_b = PL_DEF(params->depth, 1);
188 
189     float *data = datap;
190     for (int b = 0; b < dim_b; b++) {
191         for (int g = 0; g < dim_g; g++) {
192             for (int r = 0; r < dim_r; r++) {
193                 size_t offset = (b * dim_g + g) * dim_r + r;
194                 const float *src = &lut->data[offset * 3];
195                 float *dst = &data[offset * 4];
196                 dst[0] = src[0];
197                 dst[1] = src[1];
198                 dst[2] = src[2];
199                 dst[3] = 0.0f;
200             }
201         }
202     }
203 }
204 
pl_shader_custom_lut(pl_shader sh,const struct pl_custom_lut * lut,pl_shader_obj * lut_state)205 void pl_shader_custom_lut(pl_shader sh, const struct pl_custom_lut *lut,
206                           pl_shader_obj *lut_state)
207 {
208     if (!lut)
209         return;
210 
211     int dims;
212     if (lut->size[0] > 0 && lut->size[1] > 0 && lut->size[2] > 0) {
213         dims = 3;
214     } else if (lut->size[0] > 0 && !lut->size[1] && !lut->size[2]) {
215         dims = 1;
216     } else {
217         SH_FAIL(sh, "Invalid dimensions %dx%dx%d for pl_custom_lut, must be 1D "
218                 "or 3D!", lut->size[0], lut->size[1], lut->size[2]);
219         return;
220     }
221 
222     if (!sh_require(sh, PL_SHADER_SIG_COLOR, 0, 0))
223         return;
224 
225     ident_t fun = sh_lut(sh, &(struct sh_lut_params) {
226         .object = lut_state,
227         .type = PL_VAR_FLOAT,
228         .width = lut->size[0],
229         .height = lut->size[1],
230         .depth = lut->size[2],
231         .comps = 4, // for better texel alignment
232         .linear = true,
233         .signature = lut->signature,
234         .fill = fill_lut,
235         .priv = (void *) lut,
236     });
237 
238     if (!fun) {
239         SH_FAIL(sh, "pl_shader_custom_lut: failed generating LUT object");
240         return;
241     }
242 
243     GLSL("// pl_shader_custom_lut \n");
244 
245     static const struct pl_matrix3x3 zero = {0};
246     if (memcmp(&lut->shaper_in, &zero, sizeof(zero)) != 0) {
247         GLSL("color.rgb = %s * color.rgb; \n", sh_var(sh, (struct pl_shader_var) {
248             .var = pl_var_mat3("shaper_in"),
249             .data = PL_TRANSPOSE_3X3(lut->shaper_in.m),
250         }));
251     }
252 
253     switch (dims) {
254     case 1:
255         sh_describe(sh, "custom 1DLUT");
256         GLSL("color.rgb = vec3(%s(color.r).r, %s(color.g).g, %s(color.b).b); \n",
257              fun, fun, fun);
258         break;
259     case 3:
260         sh_describe(sh, "custom 3DLUT");
261         GLSL("color.rgb = %s(color.rgb).rgb; \n", fun);
262         break;
263     }
264 
265     if (memcmp(&lut->shaper_out, &zero, sizeof(zero)) != 0) {
266         GLSL("color.rgb = %s * color.rgb; \n", sh_var(sh, (struct pl_shader_var) {
267             .var = pl_var_mat3("shaper_out"),
268             .data = PL_TRANSPOSE_3X3(lut->shaper_out.m),
269         }));
270     }
271 }
272