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