Branch data Line data Source code
1 : : /*
2 : : * This file is part of libplacebo.
3 : : *
4 : : * libplacebo is free software; you can redistribute it and/or
5 : : * modify it under the terms of the GNU Lesser General Public
6 : : * License as published by the Free Software Foundation; either
7 : : * version 2.1 of the License, or (at your option) any later version.
8 : : *
9 : : * libplacebo is distributed in the hope that it will be useful,
10 : : * but WITHOUT ANY WARRANTY; without even the implied warranty of
11 : : * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 : : * GNU Lesser General Public License for more details.
13 : : *
14 : : * You should have received a copy of the GNU Lesser General Public
15 : : * License along with libplacebo. If not, see <http://www.gnu.org/licenses/>.
16 : : */
17 : :
18 : : #include <math.h>
19 : : #include <limits.h>
20 : :
21 : : #include "gpu.h"
22 : : #include "shaders.h"
23 : :
24 : : #include <libplacebo/shaders/colorspace.h>
25 : : #include <libplacebo/shaders/custom.h>
26 : :
27 : : // Hard-coded size limits, mainly for convenience (to avoid dynamic memory)
28 : : #define SHADER_MAX_HOOKS 16
29 : : #define SHADER_MAX_BINDS 16
30 : : #define MAX_SHEXP_SIZE 32
31 : :
32 : : enum shexp_op {
33 : : SHEXP_OP_ADD,
34 : : SHEXP_OP_SUB,
35 : : SHEXP_OP_MUL,
36 : : SHEXP_OP_DIV,
37 : : SHEXP_OP_MOD,
38 : : SHEXP_OP_NOT,
39 : : SHEXP_OP_GT,
40 : : SHEXP_OP_LT,
41 : : SHEXP_OP_EQ,
42 : : };
43 : :
44 : : enum shexp_tag {
45 : : SHEXP_END = 0, // End of an RPN expression
46 : : SHEXP_CONST, // Push a constant value onto the stack
47 : : SHEXP_TEX_W, // Get the width/height of a named texture (variable)
48 : : SHEXP_TEX_H,
49 : : SHEXP_OP2, // Pop two elements and push the result of a dyadic operation
50 : : SHEXP_OP1, // Pop one element and push the result of a monadic operation
51 : : SHEXP_VAR, // Arbitrary variable (e.g. shader parameters)
52 : : };
53 : :
54 : : struct shexp {
55 : : enum shexp_tag tag;
56 : : union {
57 : : float cval;
58 : : pl_str varname;
59 : : enum shexp_op op;
60 : : } val;
61 : : };
62 : :
63 : : struct custom_shader_hook {
64 : : // Variable/literal names of textures
65 : : pl_str pass_desc;
66 : : pl_str hook_tex[SHADER_MAX_HOOKS];
67 : : pl_str bind_tex[SHADER_MAX_BINDS];
68 : : pl_str save_tex;
69 : :
70 : : // Shader body itself + metadata
71 : : pl_str pass_body;
72 : : float offset[2];
73 : : bool offset_align;
74 : : int comps;
75 : :
76 : : // Special expressions governing the output size and execution conditions
77 : : struct shexp width[MAX_SHEXP_SIZE];
78 : : struct shexp height[MAX_SHEXP_SIZE];
79 : : struct shexp cond[MAX_SHEXP_SIZE];
80 : :
81 : : // Special metadata for compute shaders
82 : : bool is_compute;
83 : : int block_w, block_h; // Block size (each block corresponds to one WG)
84 : : int threads_w, threads_h; // How many threads form a WG
85 : : };
86 : :
87 : 24 : static bool parse_rpn_shexpr(pl_str line, struct shexp out[MAX_SHEXP_SIZE])
88 : : {
89 : : int pos = 0;
90 : :
91 [ + + ]: 120 : while (line.len > 0) {
92 : 96 : pl_str word = pl_str_split_char(line, ' ', &line);
93 [ + + ]: 96 : if (word.len == 0)
94 : 84 : continue;
95 : :
96 [ + - ]: 72 : if (pos >= MAX_SHEXP_SIZE)
97 : 0 : return false;
98 : :
99 : 72 : struct shexp *exp = &out[pos++];
100 : :
101 [ + + - + ]: 72 : if (pl_str_eatend0(&word, ".w") || pl_str_eatend0(&word, ".width")) {
102 : 12 : exp->tag = SHEXP_TEX_W;
103 : 12 : exp->val.varname = word;
104 : 12 : continue;
105 : : }
106 : :
107 [ + + - + ]: 60 : if (pl_str_eatend0(&word, ".h") || pl_str_eatend0(&word, ".height")) {
108 : 4 : exp->tag = SHEXP_TEX_H;
109 : 4 : exp->val.varname = word;
110 : 4 : continue;
111 : : }
112 : :
113 [ - - + - : 56 : switch (word.buf[0]) {
- - + + +
+ ]
114 : 0 : case '+': exp->tag = SHEXP_OP2; exp->val.op = SHEXP_OP_ADD; continue;
115 : 0 : case '-': exp->tag = SHEXP_OP2; exp->val.op = SHEXP_OP_SUB; continue;
116 : 8 : case '*': exp->tag = SHEXP_OP2; exp->val.op = SHEXP_OP_MUL; continue;
117 : 0 : case '/': exp->tag = SHEXP_OP2; exp->val.op = SHEXP_OP_DIV; continue;
118 : 0 : case '%': exp->tag = SHEXP_OP2; exp->val.op = SHEXP_OP_MOD; continue;
119 : 0 : case '!': exp->tag = SHEXP_OP1; exp->val.op = SHEXP_OP_NOT; continue;
120 : 4 : case '>': exp->tag = SHEXP_OP2; exp->val.op = SHEXP_OP_GT; continue;
121 : 8 : case '<': exp->tag = SHEXP_OP2; exp->val.op = SHEXP_OP_LT; continue;
122 : 4 : case '=': exp->tag = SHEXP_OP2; exp->val.op = SHEXP_OP_EQ; continue;
123 : : }
124 : :
125 [ + + ]: 32 : if (word.buf[0] >= '0' && word.buf[0] <= '9') {
126 : 20 : exp->tag = SHEXP_CONST;
127 [ + - ]: 20 : if (!pl_str_parse_float(word, &exp->val.cval))
128 : : return false;
129 : 20 : continue;
130 : : }
131 : :
132 : : // Treat as generic variable
133 : 12 : exp->tag = SHEXP_VAR;
134 : 12 : exp->val.varname = word;
135 : : }
136 : :
137 : : return true;
138 : : }
139 : :
140 : 48 : static inline pl_str split_magic(pl_str *body)
141 : : {
142 : 48 : pl_str ret = pl_str_split_str0(*body, "//!", body);
143 [ + + ]: 48 : if (body->len) {
144 : : // Make sure the separator is included in the remainder
145 : 38 : body->buf -= 3;
146 : 38 : body->len += 3;
147 : : }
148 : :
149 : 48 : return ret;
150 : : }
151 : :
152 : 22 : static bool parse_hook(pl_log log, pl_str *body, struct custom_shader_hook *out)
153 : : {
154 : 22 : *out = (struct custom_shader_hook){
155 : : .pass_desc = pl_str0("unknown user shader"),
156 : : .width = {{ SHEXP_TEX_W, { .varname = pl_str0("HOOKED") }}},
157 : : .height = {{ SHEXP_TEX_H, { .varname = pl_str0("HOOKED") }}},
158 : : .cond = {{ SHEXP_CONST, { .cval = 1.0 }}},
159 : : };
160 : :
161 : : int hook_idx = 0;
162 : : int bind_idx = 0;
163 : :
164 : : // Parse all headers
165 : 92 : while (true) {
166 : : pl_str rest;
167 : 114 : pl_str line = pl_str_strip(pl_str_getline(*body, &rest));
168 : :
169 : : // Check for the presence of the magic line beginning
170 [ + + ]: 114 : if (!pl_str_eatstart0(&line, "//!"))
171 : : break;
172 : :
173 : 92 : *body = rest;
174 : :
175 : : // Parse the supported commands
176 [ + + ]: 92 : if (pl_str_eatstart0(&line, "HOOK")) {
177 [ - + ]: 22 : if (hook_idx == SHADER_MAX_HOOKS) {
178 : 0 : pl_err(log, "Passes may only hook up to %d textures!",
179 : : SHADER_MAX_HOOKS);
180 : 0 : return false;
181 : : }
182 : 22 : out->hook_tex[hook_idx++] = pl_str_strip(line);
183 : 92 : continue;
184 : : }
185 : :
186 [ + + ]: 70 : if (pl_str_eatstart0(&line, "BIND")) {
187 [ - + ]: 22 : if (bind_idx == SHADER_MAX_BINDS) {
188 : 0 : pl_err(log, "Passes may only bind up to %d textures!",
189 : : SHADER_MAX_BINDS);
190 : 0 : return false;
191 : : }
192 : 22 : out->bind_tex[bind_idx++] = pl_str_strip(line);
193 : 22 : continue;
194 : : }
195 : :
196 [ + + ]: 52 : if (pl_str_eatstart0(&line, "SAVE")) {
197 : 4 : pl_str save_tex = pl_str_strip(line);
198 [ - + ]: 4 : if (pl_str_equals0(save_tex, "HOOKED")) {
199 : : // This is a special name that means "overwrite existing"
200 : : // texture, which we just signal by not having any `save_tex`
201 : : // name set.
202 : 0 : out->save_tex = (pl_str) {0};
203 [ - + ]: 4 : } else if (pl_str_equals0(save_tex, "MAIN")) {
204 : : // Compatibility alias
205 : 0 : out->save_tex = pl_str0("MAINPRESUB");
206 : : } else {
207 : 4 : out->save_tex = save_tex;
208 : : };
209 : : continue;
210 : : }
211 : :
212 [ + + ]: 44 : if (pl_str_eatstart0(&line, "DESC")) {
213 : 14 : out->pass_desc = pl_str_strip(line);
214 : 14 : continue;
215 : : }
216 : :
217 [ - + ]: 30 : if (pl_str_eatstart0(&line, "OFFSET")) {
218 : 0 : line = pl_str_strip(line);
219 [ # # ]: 0 : if (pl_str_equals0(line, "ALIGN")) {
220 : 0 : out->offset_align = true;
221 : : } else {
222 [ # # # # ]: 0 : if (!pl_str_parse_float(pl_str_split_char(line, ' ', &line), &out->offset[0]) ||
223 : 0 : !pl_str_parse_float(pl_str_split_char(line, ' ', &line), &out->offset[1]) ||
224 [ # # ]: 0 : line.len)
225 : : {
226 : 0 : pl_err(log, "Error while parsing OFFSET!");
227 : 0 : return false;
228 : : }
229 : : }
230 : 0 : continue;
231 : : }
232 : :
233 [ + + ]: 30 : if (pl_str_eatstart0(&line, "WIDTH")) {
234 [ - + ]: 4 : if (!parse_rpn_shexpr(line, out->width)) {
235 : 0 : pl_err(log, "Error while parsing WIDTH!");
236 : 0 : return false;
237 : : }
238 : 4 : continue;
239 : : }
240 : :
241 [ + + ]: 26 : if (pl_str_eatstart0(&line, "HEIGHT")) {
242 [ - + ]: 4 : if (!parse_rpn_shexpr(line, out->height)) {
243 : 0 : pl_err(log, "Error while parsing HEIGHT!");
244 : 0 : return false;
245 : : }
246 : 4 : continue;
247 : : }
248 : :
249 [ + + ]: 22 : if (pl_str_eatstart0(&line, "WHEN")) {
250 [ - + ]: 16 : if (!parse_rpn_shexpr(line, out->cond)) {
251 : 0 : pl_err(log, "Error while parsing WHEN!");
252 : 0 : return false;
253 : : }
254 : 16 : continue;
255 : : }
256 : :
257 [ + - ]: 6 : if (pl_str_eatstart0(&line, "COMPONENTS")) {
258 [ - + ]: 6 : if (!pl_str_parse_int(pl_str_strip(line), &out->comps)) {
259 [ # # ]: 0 : pl_err(log, "Error parsing COMPONENTS: '%.*s'", PL_STR_FMT(line));
260 : 0 : return false;
261 : : }
262 : 6 : continue;
263 : : }
264 : :
265 [ # # ]: 0 : if (pl_str_eatstart0(&line, "COMPUTE")) {
266 : 0 : line = pl_str_strip(line);
267 [ # # # # ]: 0 : bool ok = pl_str_parse_int(pl_str_split_char(line, ' ', &line), &out->block_w) &&
268 : 0 : pl_str_parse_int(pl_str_split_char(line, ' ', &line), &out->block_h);
269 : :
270 : 0 : line = pl_str_strip(line);
271 [ # # # # ]: 0 : if (ok && line.len) {
272 [ # # ]: 0 : ok = pl_str_parse_int(pl_str_split_char(line, ' ', &line), &out->threads_w) &&
273 [ # # ]: 0 : pl_str_parse_int(pl_str_split_char(line, ' ', &line), &out->threads_h) &&
274 [ # # ]: 0 : !line.len;
275 : : } else {
276 : 0 : out->threads_w = out->block_w;
277 : 0 : out->threads_h = out->block_h;
278 : : }
279 : :
280 [ # # ]: 0 : if (!ok) {
281 : 0 : pl_err(log, "Error while parsing COMPUTE!");
282 : 0 : return false;
283 : : }
284 : :
285 : 0 : out->is_compute = true;
286 : 0 : continue;
287 : : }
288 : :
289 : : // Unknown command type
290 [ # # ]: 0 : pl_err(log, "Unrecognized command '%.*s'!", PL_STR_FMT(line));
291 : 0 : return false;
292 : : }
293 : :
294 : : // The rest of the file up until the next magic line beginning (if any)
295 : : // shall be the shader body
296 : 22 : out->pass_body = split_magic(body);
297 : :
298 : : // Sanity checking
299 [ - + ]: 22 : if (hook_idx == 0)
300 : 0 : pl_warn(log, "Pass has no hooked textures (will be ignored)!");
301 : :
302 : : return true;
303 : : }
304 : :
305 : 6 : static bool parse_tex(pl_gpu gpu, void *alloc, pl_str *body,
306 : : struct pl_shader_desc *out)
307 : : {
308 : 6 : *out = (struct pl_shader_desc) {
309 : : .desc = {
310 : : .name = "USER_TEX",
311 : : .type = PL_DESC_SAMPLED_TEX,
312 : : },
313 : : };
314 : :
315 : 6 : struct pl_tex_params params = {
316 : : .w = 1, .h = 1, .d = 0,
317 : : .sampleable = true,
318 : : .debug_tag = PL_DEBUG_TAG,
319 : : };
320 : :
321 : 28 : while (true) {
322 : : pl_str rest;
323 : 34 : pl_str line = pl_str_strip(pl_str_getline(*body, &rest));
324 : :
325 [ + + ]: 34 : if (!pl_str_eatstart0(&line, "//!"))
326 : : break;
327 : :
328 : 28 : *body = rest;
329 : :
330 [ + + ]: 28 : if (pl_str_eatstart0(&line, "TEXTURE")) {
331 : 6 : out->desc.name = pl_strdup0(alloc, pl_str_strip(line));
332 : 28 : continue;
333 : : }
334 : :
335 [ + + ]: 22 : if (pl_str_eatstart0(&line, "SIZE")) {
336 : 6 : line = pl_str_strip(line);
337 : : int dims = 0;
338 : : int dim[4]; // extra space to catch invalid extra entries
339 [ + + + - ]: 18 : while (line.len && dims < PL_ARRAY_SIZE(dim)) {
340 [ - + ]: 12 : if (!pl_str_parse_int(pl_str_split_char(line, ' ', &line), &dim[dims++])) {
341 : 0 : PL_ERR(gpu, "Error while parsing SIZE!");
342 : 0 : return false;
343 : : }
344 : : }
345 : :
346 : : uint32_t lim = dims == 1 ? gpu->limits.max_tex_1d_dim
347 [ - + ]: 6 : : dims == 2 ? gpu->limits.max_tex_2d_dim
348 [ + - ]: 6 : : dims == 3 ? gpu->limits.max_tex_3d_dim
349 [ # # ]: 0 : : 0;
350 : :
351 : : // Sanity check against GPU size limits
352 [ - + - - ]: 6 : switch (dims) {
353 : 0 : case 3:
354 : 0 : params.d = dim[2];
355 [ # # # # ]: 0 : if (params.d < 1 || params.d > lim) {
356 : 0 : PL_ERR(gpu, "SIZE %d exceeds GPU's texture size limits (%d)!",
357 : : params.d, lim);
358 : 0 : return false;
359 : : }
360 : : // fall through
361 : : case 2:
362 : 6 : params.h = dim[1];
363 [ + - - + ]: 6 : if (params.h < 1 || params.h > lim) {
364 : 0 : PL_ERR(gpu, "SIZE %d exceeds GPU's texture size limits (%d)!",
365 : : params.h, lim);
366 : 0 : return false;
367 : : }
368 : : // fall through
369 : : case 1:
370 : 6 : params.w = dim[0];
371 [ + - - + ]: 6 : if (params.w < 1 || params.w > lim) {
372 : 0 : PL_ERR(gpu, "SIZE %d exceeds GPU's texture size limits (%d)!",
373 : : params.w, lim);
374 : 0 : return false;
375 : : }
376 : : break;
377 : :
378 : 0 : default:
379 : 0 : PL_ERR(gpu, "Invalid number of texture dimensions!");
380 : 0 : return false;
381 : : };
382 : :
383 : : // Clear out the superfluous components
384 [ + - ]: 6 : if (dims < 3)
385 : 6 : params.d = 0;
386 [ - + ]: 6 : if (dims < 2)
387 : 0 : params.h = 0;
388 : 6 : continue;
389 : : }
390 : :
391 [ + + ]: 16 : if (pl_str_eatstart0(&line, "FORMAT")) {
392 : 6 : line = pl_str_strip(line);
393 : 6 : params.format = NULL;
394 [ + - ]: 44 : for (int n = 0; n < gpu->num_formats; n++) {
395 : 44 : pl_fmt fmt = gpu->formats[n];
396 [ + + ]: 44 : if (pl_str_equals0(line, fmt->name)) {
397 : 6 : params.format = fmt;
398 : 6 : break;
399 : : }
400 : : }
401 : :
402 [ + - - + ]: 6 : if (!params.format || params.format->opaque) {
403 [ # # ]: 0 : PL_ERR(gpu, "Unrecognized/unavailable FORMAT name: '%.*s'!",
404 : : PL_STR_FMT(line));
405 : 0 : return false;
406 : : }
407 : :
408 [ - + ]: 6 : if (!(params.format->caps & PL_FMT_CAP_SAMPLEABLE)) {
409 [ # # ]: 0 : PL_ERR(gpu, "Chosen FORMAT '%.*s' is not sampleable!",
410 : : PL_STR_FMT(line));
411 : 0 : return false;
412 : : }
413 : 6 : continue;
414 : : }
415 : :
416 [ + + ]: 10 : if (pl_str_eatstart0(&line, "FILTER")) {
417 : 4 : line = pl_str_strip(line);
418 [ - + ]: 4 : if (pl_str_equals0(line, "LINEAR")) {
419 : 0 : out->binding.sample_mode = PL_TEX_SAMPLE_LINEAR;
420 [ + - ]: 4 : } else if (pl_str_equals0(line, "NEAREST")) {
421 : 4 : out->binding.sample_mode = PL_TEX_SAMPLE_NEAREST;
422 : : } else {
423 [ # # ]: 0 : PL_ERR(gpu, "Unrecognized FILTER: '%.*s'!", PL_STR_FMT(line));
424 : 0 : return false;
425 : : }
426 : 4 : continue;
427 : : }
428 : :
429 [ + + ]: 6 : if (pl_str_eatstart0(&line, "BORDER")) {
430 : 4 : line = pl_str_strip(line);
431 [ - + ]: 4 : if (pl_str_equals0(line, "CLAMP")) {
432 : 0 : out->binding.address_mode = PL_TEX_ADDRESS_CLAMP;
433 [ + - ]: 4 : } else if (pl_str_equals0(line, "REPEAT")) {
434 : 4 : out->binding.address_mode = PL_TEX_ADDRESS_REPEAT;
435 [ # # ]: 0 : } else if (pl_str_equals0(line, "MIRROR")) {
436 : 0 : out->binding.address_mode = PL_TEX_ADDRESS_MIRROR;
437 : : } else {
438 [ # # ]: 0 : PL_ERR(gpu, "Unrecognized BORDER: '%.*s'!", PL_STR_FMT(line));
439 : 0 : return false;
440 : : }
441 : 4 : continue;
442 : : }
443 : :
444 [ + - ]: 2 : if (pl_str_eatstart0(&line, "STORAGE")) {
445 : 2 : params.storable = true;
446 : 2 : out->desc.type = PL_DESC_STORAGE_IMG;
447 : 2 : out->desc.access = PL_DESC_ACCESS_READWRITE;
448 : 2 : out->memory = PL_MEMORY_COHERENT;
449 : 2 : continue;
450 : : }
451 : :
452 [ # # ]: 0 : PL_ERR(gpu, "Unrecognized command '%.*s'!", PL_STR_FMT(line));
453 : 0 : return false;
454 : : }
455 : :
456 [ - + ]: 6 : if (!params.format) {
457 : 0 : PL_ERR(gpu, "No FORMAT specified!");
458 : 0 : return false;
459 : : }
460 : :
461 : 6 : int caps = params.format->caps;
462 [ - + - - ]: 6 : if (out->binding.sample_mode == PL_TEX_SAMPLE_LINEAR && !(caps & PL_FMT_CAP_LINEAR)) {
463 : 0 : PL_ERR(gpu, "The specified texture format cannot be linear filtered!");
464 : 0 : return false;
465 : : }
466 : :
467 : : // Decode the rest of the section (up to the next //! marker) as raw hex
468 : : // data for the texture
469 : 6 : pl_str tex, hexdata = split_magic(body);
470 [ - + ]: 6 : if (!pl_str_decode_hex(NULL, pl_str_strip(hexdata), &tex)) {
471 : 0 : PL_ERR(gpu, "Error while parsing TEXTURE body: must be a valid "
472 : : "hexadecimal sequence!");
473 : 0 : return false;
474 : : }
475 : :
476 [ + - - + ]: 12 : int texels = params.w * PL_DEF(params.h, 1) * PL_DEF(params.d, 1);
477 : 6 : size_t expected_len = texels * params.format->texel_size;
478 [ + + + - ]: 6 : if (tex.len == 0 && params.storable) {
479 : : // In this case, it's okay that the texture has no initial data
480 : 2 : pl_free_ptr(&tex.buf);
481 [ - + ]: 4 : } else if (tex.len != expected_len) {
482 : 0 : PL_ERR(gpu, "Shader TEXTURE size mismatch: got %zu bytes, expected %zu!",
483 : : tex.len, expected_len);
484 : 0 : pl_free(tex.buf);
485 : 0 : return false;
486 : : }
487 : :
488 : 6 : params.initial_data = tex.buf;
489 : 6 : out->binding.object = pl_tex_create(gpu, ¶ms);
490 : 6 : pl_free(tex.buf);
491 : :
492 [ - + ]: 6 : if (!out->binding.object) {
493 : 0 : PL_ERR(gpu, "Failed creating custom texture!");
494 : 0 : return false;
495 : : }
496 : :
497 : : return true;
498 : : }
499 : :
500 : 4 : static bool parse_buf(pl_gpu gpu, void *alloc, pl_str *body,
501 : : struct pl_shader_desc *out)
502 : : {
503 : 4 : *out = (struct pl_shader_desc) {
504 : : .desc = {
505 : : .name = "USER_BUF",
506 : : .type = PL_DESC_BUF_UNIFORM,
507 : : },
508 : : };
509 : :
510 : : // Temporary, to allow deferring variable placement until all headers
511 : : // have been processed (in order to e.g. determine buffer type)
512 : 4 : void *tmp = pl_tmp(alloc); // will be freed automatically on failure
513 : : PL_ARRAY(struct pl_var) vars = {0};
514 : :
515 : 14 : while (true) {
516 : : pl_str rest;
517 : 18 : pl_str line = pl_str_strip(pl_str_getline(*body, &rest));
518 : :
519 [ + + ]: 18 : if (!pl_str_eatstart0(&line, "//!"))
520 : : break;
521 : :
522 : 14 : *body = rest;
523 : :
524 [ + + ]: 14 : if (pl_str_eatstart0(&line, "BUFFER")) {
525 : 4 : out->desc.name = pl_strdup0(alloc, pl_str_strip(line));
526 : 18 : continue;
527 : : }
528 : :
529 [ + + ]: 10 : if (pl_str_eatstart0(&line, "STORAGE")) {
530 : 2 : out->desc.type = PL_DESC_BUF_STORAGE;
531 : 2 : out->desc.access = PL_DESC_ACCESS_READWRITE;
532 : 2 : out->memory = PL_MEMORY_COHERENT;
533 : 2 : continue;
534 : : }
535 : :
536 [ + - ]: 8 : if (pl_str_eatstart0(&line, "VAR")) {
537 : 8 : pl_str type_name = pl_str_split_char(pl_str_strip(line), ' ', &line);
538 : 8 : struct pl_var var = {0};
539 [ + - ]: 34 : for (const struct pl_named_var *nv = pl_var_glsl_types; nv->glsl_name; nv++) {
540 [ + + ]: 34 : if (pl_str_equals0(type_name, nv->glsl_name)) {
541 : 8 : var = nv->var;
542 : 8 : break;
543 : : }
544 : : }
545 : :
546 [ - + ]: 8 : if (!var.type) {
547 : : // No type found
548 [ # # ]: 0 : PL_ERR(gpu, "Unrecognized GLSL type '%.*s'!", PL_STR_FMT(type_name));
549 : 0 : return false;
550 : : }
551 : :
552 : 8 : pl_str var_name = pl_str_split_char(line, '[', &line);
553 [ + + ]: 8 : if (line.len > 0) {
554 : : // Parse array dimension
555 [ - + ]: 2 : if (!pl_str_parse_int(pl_str_split_char(line, ']', NULL), &var.dim_a)) {
556 [ # # ]: 0 : PL_ERR(gpu, "Failed parsing array dimension from [%.*s!",
557 : : PL_STR_FMT(line));
558 : 0 : return false;
559 : : }
560 : :
561 [ - + ]: 2 : if (var.dim_a < 1) {
562 : 0 : PL_ERR(gpu, "Invalid array dimension %d!", var.dim_a);
563 : 0 : return false;
564 : : }
565 : : }
566 : :
567 : 8 : var.name = pl_strdup0(alloc, pl_str_strip(var_name));
568 [ + + - + : 8 : PL_ARRAY_APPEND(tmp, vars, var);
- + ]
569 : 8 : continue;
570 : : }
571 : :
572 [ # # ]: 0 : PL_ERR(gpu, "Unrecognized command '%.*s'!", PL_STR_FMT(line));
573 : 0 : return false;
574 : : }
575 : :
576 : : // Try placing all of the buffer variables
577 [ + + ]: 12 : for (int i = 0; i < vars.num; i++) {
578 [ - + ]: 8 : if (!sh_buf_desc_append(alloc, gpu, out, NULL, vars.elem[i])) {
579 : 0 : PL_ERR(gpu, "Custom buffer exceeds GPU limitations!");
580 : 0 : return false;
581 : : }
582 : : }
583 : :
584 : : // Decode the rest of the section (up to the next //! marker) as raw hex
585 : : // data for the buffer
586 : 4 : pl_str data, hexdata = split_magic(body);
587 [ - + ]: 4 : if (!pl_str_decode_hex(tmp, pl_str_strip(hexdata), &data)) {
588 : 0 : PL_ERR(gpu, "Error while parsing BUFFER body: must be a valid "
589 : : "hexadecimal sequence!");
590 : 0 : return false;
591 : : }
592 : :
593 : 4 : size_t buf_size = sh_buf_desc_size(out);
594 [ + + - + ]: 4 : if (data.len == 0 && out->desc.type == PL_DESC_BUF_STORAGE) {
595 : : // In this case, it's okay that the buffer has no initial data
596 [ - + ]: 2 : } else if (data.len != buf_size) {
597 : 0 : PL_ERR(gpu, "Shader BUFFER size mismatch: got %zu bytes, expected %zu!",
598 : : data.len, buf_size);
599 : 0 : return false;
600 : : }
601 : :
602 [ + + ]: 4 : out->binding.object = pl_buf_create(gpu, pl_buf_params(
603 : : .size = buf_size,
604 : : .uniform = out->desc.type == PL_DESC_BUF_UNIFORM,
605 : : .storable = out->desc.type == PL_DESC_BUF_STORAGE,
606 : : .initial_data = data.len ? data.buf : NULL,
607 : : ));
608 : :
609 [ - + ]: 4 : if (!out->binding.object) {
610 : 0 : PL_ERR(gpu, "Failed creating custom buffer!");
611 : 0 : return false;
612 : : }
613 : :
614 : 4 : pl_free(tmp);
615 : 4 : return true;
616 : : }
617 : :
618 : 36 : static bool parse_var(pl_log log, pl_str str, enum pl_var_type type, pl_var_data *out)
619 : : {
620 [ + + ]: 36 : if (!str.len)
621 : : return true;
622 : :
623 : 24 : pl_str buf = str;
624 : : bool ok = false;
625 [ + + + - : 24 : switch (type) {
- ]
626 : 4 : case PL_VAR_SINT:
627 : 4 : ok = pl_str_parse_int(pl_str_split_char(buf, ' ', &buf), &out->i);
628 : 4 : break;
629 : 8 : case PL_VAR_UINT:
630 : 8 : ok = pl_str_parse_uint(pl_str_split_char(buf, ' ', &buf), &out->u);
631 : 8 : break;
632 : 12 : case PL_VAR_FLOAT:
633 : 12 : ok = pl_str_parse_float(pl_str_split_char(buf, ' ', &buf), &out->f);
634 : 12 : break;
635 : : case PL_VAR_INVALID:
636 : : case PL_VAR_TYPE_COUNT:
637 : 0 : pl_unreachable();
638 : : }
639 : :
640 [ + - ]: 24 : if (pl_str_strip(buf).len > 0)
641 : : ok = false; // left-over garbage
642 : :
643 [ + - ]: 24 : if (!ok) {
644 [ # # ]: 0 : pl_err(log, "Failed parsing variable data: %.*s", PL_STR_FMT(str));
645 : 0 : return false;
646 : : }
647 : :
648 : : return true;
649 : : }
650 : :
651 : 12 : static bool check_bounds(pl_log log, enum pl_var_type type, const pl_var_data data,
652 : : const pl_var_data minimum, const pl_var_data maximum)
653 : : {
654 : : #define CHECK_BOUNDS(v, fmt) do \
655 : : { \
656 : : if (data.v < minimum.v) { \
657 : : pl_err(log, "Initial value "fmt" below declared minimum "fmt"!", \
658 : : data.v, minimum.v); \
659 : : return false; \
660 : : } \
661 : : if (data.v > maximum.v) { \
662 : : pl_err(log, "Initial value "fmt" above declared maximum "fmt"!", \
663 : : data.v, maximum.v); \
664 : : return false; \
665 : : } \
666 : : } while (0)
667 : :
668 [ + + + - : 12 : switch (type) {
- ]
669 : 4 : case PL_VAR_SINT:
670 [ - + - + ]: 4 : CHECK_BOUNDS(i, "%d");
671 : : break;
672 : 4 : case PL_VAR_UINT:
673 [ - + - + ]: 4 : CHECK_BOUNDS(u, "%u");
674 : : break;
675 : 4 : case PL_VAR_FLOAT:
676 [ - + - + ]: 4 : CHECK_BOUNDS(f, "%f");
677 : : break;
678 : : case PL_VAR_INVALID:
679 : : case PL_VAR_TYPE_COUNT:
680 : 0 : pl_unreachable();
681 : : }
682 : :
683 : : #undef CHECK_BOUNDS
684 : : return true;
685 : : }
686 : :
687 : 16 : static bool parse_param(pl_log log, void *alloc, pl_str *body,
688 : : struct pl_hook_par *out)
689 : : {
690 : 16 : *out = (struct pl_hook_par) {0};
691 : 16 : pl_str minimum = {0};
692 : 16 : pl_str maximum = {0};
693 : : bool is_enum = false;
694 : :
695 : : while (true) {
696 : : pl_str rest;
697 : 64 : pl_str line = pl_str_strip(pl_str_getline(*body, &rest));
698 : :
699 [ + + ]: 64 : if (!pl_str_eatstart0(&line, "//!"))
700 : : break;
701 : :
702 : 48 : *body = rest;
703 : :
704 [ + + ]: 48 : if (pl_str_eatstart0(&line, "PARAM")) {
705 : 16 : out->name = pl_strdup0(alloc, pl_str_strip(line));
706 : 56 : continue;
707 : : }
708 : :
709 [ + + ]: 32 : if (pl_str_eatstart0(&line, "DESC")) {
710 : 4 : out->description = pl_strdup0(alloc, pl_str_strip(line));
711 : 4 : continue;
712 : : }
713 : :
714 [ + + ]: 28 : if (pl_str_eatstart0(&line, "MINIMUM")) {
715 : 4 : minimum = pl_str_strip(line);
716 : 4 : continue;
717 : : }
718 : :
719 [ + + ]: 24 : if (pl_str_eatstart0(&line, "MAXIMUM")) {
720 : 8 : maximum = pl_str_strip(line);
721 : 8 : continue;
722 : : }
723 : :
724 [ + - ]: 16 : if (pl_str_eatstart0(&line, "TYPE")) {
725 : 16 : line = pl_str_strip(line);
726 : 16 : is_enum = pl_str_eatstart0(&line, "ENUM");
727 : 16 : line = pl_str_strip(line);
728 [ + + ]: 16 : if (pl_str_eatstart0(&line, "DYNAMIC")) {
729 : 4 : out->mode = PL_HOOK_PAR_DYNAMIC;
730 [ + + ]: 12 : } else if (pl_str_eatstart0(&line, "CONSTANT")) {
731 : 4 : out->mode = PL_HOOK_PAR_CONSTANT;
732 [ + - ]: 8 : } else if (pl_str_eatstart0(&line, "DEFINE")) {
733 : 8 : out->mode = PL_HOOK_PAR_DEFINE;
734 : 8 : out->type = PL_VAR_SINT;
735 [ - + ]: 8 : if (pl_str_strip(line).len > 0) {
736 [ # # ]: 0 : pl_err(log, "TYPE DEFINE does not take any extra arguments, "
737 : : "unexpected: '%.*s'", PL_STR_FMT(line));
738 : 0 : return false;
739 : : }
740 : 8 : continue;
741 : : } else {
742 : 0 : out->mode = PL_HOOK_PAR_VARIABLE;
743 : : }
744 : :
745 : 8 : line = pl_str_strip(line);
746 : 8 : for (const struct pl_named_var *nv = pl_var_glsl_types;
747 [ + - ]: 72 : nv->glsl_name; nv++)
748 : : {
749 [ + + ]: 72 : if (pl_str_equals0(line, nv->glsl_name)) {
750 [ + - - + ]: 8 : if (nv->var.dim_v > 1 || nv->var.dim_m > 1) {
751 : 0 : pl_err(log, "GLSL type '%s' is incompatible with "
752 : : "shader parameters, must be scalar type!",
753 : : nv->glsl_name);
754 : 0 : return false;
755 : : }
756 : :
757 : 8 : out->type = nv->var.type;
758 [ - + - - ]: 8 : if (is_enum && out->type != PL_VAR_SINT) {
759 : 0 : pl_err(log, "ENUM is only compatible with type int/DEFINE!");
760 : 0 : return false;
761 : : }
762 : 8 : goto next;
763 : : }
764 : : }
765 : :
766 [ # # ]: 0 : pl_err(log, "Unrecognized GLSL type '%.*s'!", PL_STR_FMT(line));
767 : 0 : return false;
768 : : }
769 : :
770 [ # # ]: 0 : pl_err(log, "Unrecognized command '%.*s'!", PL_STR_FMT(line));
771 : 0 : return false;
772 : :
773 : : next: ;
774 : : }
775 : :
776 [ - + + + : 16 : switch (out->type) {
- - ]
777 : 0 : case PL_VAR_INVALID:
778 : 0 : pl_err(log, "Missing variable type!");
779 : 0 : return false;
780 : 8 : case PL_VAR_SINT:
781 : 8 : out->minimum.i = INT_MIN;
782 : 8 : out->maximum.i = INT_MAX;
783 : 8 : break;
784 : 4 : case PL_VAR_UINT:
785 : 4 : out->minimum.u = 0;
786 : 4 : out->maximum.u = UINT_MAX;
787 : 4 : break;
788 : 4 : case PL_VAR_FLOAT:
789 : 4 : out->minimum.f = -INFINITY;
790 : 4 : out->maximum.f = INFINITY;
791 : 4 : break;
792 : : case PL_VAR_TYPE_COUNT:
793 : 0 : pl_unreachable();
794 : : }
795 : :
796 : 16 : pl_str initial = pl_str_strip(split_magic(body));
797 [ - + ]: 16 : if (!initial.len) {
798 : 0 : pl_err(log, "Missing initial parameter value!");
799 : 0 : return false;
800 : : }
801 : :
802 [ + + ]: 16 : if (is_enum) {
803 : : PL_ARRAY(const char *) names = {0};
804 [ - + ]: 4 : pl_assert(out->type == PL_VAR_SINT);
805 : : do {
806 : 8 : pl_str line = pl_str_strip(pl_str_getline(initial, &initial));
807 [ - + ]: 8 : if (!line.len)
808 : 0 : continue;
809 [ + + - + : 8 : PL_ARRAY_APPEND(alloc, names, pl_strdup0(alloc, line));
- + ]
810 [ + + ]: 8 : } while (initial.len);
811 : :
812 [ - + ]: 4 : pl_assert(names.num >= 1);
813 : 4 : out->initial.i = 0;
814 : 4 : out->minimum.i = 0;
815 : 4 : out->maximum.i = names.num - 1;
816 : 4 : out->names = names.elem;
817 : : } else {
818 [ - + ]: 12 : if (!parse_var(log, initial, out->type, &out->initial))
819 : : return false;
820 [ - + ]: 12 : if (!parse_var(log, minimum, out->type, &out->minimum))
821 : : return false;
822 [ - + ]: 12 : if (!parse_var(log, maximum, out->type, &out->maximum))
823 : : return false;
824 [ - + ]: 12 : if (!check_bounds(log, out->type, out->initial, out->minimum, out->maximum))
825 : : return false;
826 : : }
827 : :
828 : 16 : out->data = pl_memdup(alloc, &out->initial, sizeof(out->initial));
829 : 16 : return true;
830 : : }
831 : :
832 : 756 : static enum pl_hook_stage mp_stage_to_pl(pl_str stage)
833 : : {
834 [ + - ]: 756 : if (pl_str_equals0(stage, "RGB"))
835 : : return PL_HOOK_RGB_INPUT;
836 [ + - ]: 756 : if (pl_str_equals0(stage, "LUMA"))
837 : : return PL_HOOK_LUMA_INPUT;
838 [ + - ]: 756 : if (pl_str_equals0(stage, "CHROMA"))
839 : : return PL_HOOK_CHROMA_INPUT;
840 [ + - ]: 756 : if (pl_str_equals0(stage, "ALPHA"))
841 : : return PL_HOOK_ALPHA_INPUT;
842 [ + - ]: 756 : if (pl_str_equals0(stage, "XYZ"))
843 : : return PL_HOOK_XYZ_INPUT;
844 : :
845 [ + - ]: 756 : if (pl_str_equals0(stage, "CHROMA_SCALED"))
846 : : return PL_HOOK_CHROMA_SCALED;
847 [ + - ]: 756 : if (pl_str_equals0(stage, "ALPHA_SCALED"))
848 : : return PL_HOOK_ALPHA_SCALED;
849 : :
850 [ + + ]: 756 : if (pl_str_equals0(stage, "NATIVE"))
851 : : return PL_HOOK_NATIVE;
852 [ + - ]: 744 : if (pl_str_equals0(stage, "MAINPRESUB"))
853 : : return PL_HOOK_RGB;
854 [ + + ]: 744 : if (pl_str_equals0(stage, "MAIN"))
855 : : return PL_HOOK_RGB; // Note: conflicts with above!
856 : :
857 [ + - ]: 726 : if (pl_str_equals0(stage, "LINEAR"))
858 : : return PL_HOOK_LINEAR;
859 [ + - ]: 726 : if (pl_str_equals0(stage, "SIGMOID"))
860 : : return PL_HOOK_SIGMOID;
861 [ + - ]: 726 : if (pl_str_equals0(stage, "PREKERNEL"))
862 : : return PL_HOOK_PRE_KERNEL;
863 [ + - ]: 726 : if (pl_str_equals0(stage, "POSTKERNEL"))
864 : : return PL_HOOK_POST_KERNEL;
865 : :
866 [ + - ]: 726 : if (pl_str_equals0(stage, "SCALED"))
867 : : return PL_HOOK_SCALED;
868 [ + - ]: 726 : if (pl_str_equals0(stage, "PREOUTPUT"))
869 : : return PL_HOOK_PRE_OUTPUT;
870 [ - + ]: 726 : if (pl_str_equals0(stage, "OUTPUT"))
871 : 0 : return PL_HOOK_OUTPUT;
872 : :
873 : : return 0;
874 : : }
875 : :
876 : 14 : static pl_str pl_stage_to_mp(enum pl_hook_stage stage)
877 : : {
878 [ - - - - : 14 : switch (stage) {
- - - + +
- - - - -
- - - ]
879 : : case PL_HOOK_RGB_INPUT: return pl_str0("RGB");
880 : : case PL_HOOK_LUMA_INPUT: return pl_str0("LUMA");
881 : : case PL_HOOK_CHROMA_INPUT: return pl_str0("CHROMA");
882 : : case PL_HOOK_ALPHA_INPUT: return pl_str0("ALPHA");
883 : : case PL_HOOK_XYZ_INPUT: return pl_str0("XYZ");
884 : :
885 : : case PL_HOOK_CHROMA_SCALED: return pl_str0("CHROMA_SCALED");
886 : : case PL_HOOK_ALPHA_SCALED: return pl_str0("ALPHA_SCALED");
887 : :
888 : : case PL_HOOK_NATIVE: return pl_str0("NATIVE");
889 : : case PL_HOOK_RGB: return pl_str0("MAINPRESUB");
890 : :
891 : : case PL_HOOK_LINEAR: return pl_str0("LINEAR");
892 : : case PL_HOOK_SIGMOID: return pl_str0("SIGMOID");
893 : : case PL_HOOK_PRE_KERNEL: return pl_str0("PREKERNEL");
894 : : case PL_HOOK_POST_KERNEL: return pl_str0("POSTKERNEL");
895 : :
896 : : case PL_HOOK_SCALED: return pl_str0("SCALED");
897 : : case PL_HOOK_PRE_OUTPUT: return pl_str0("PREOUTPUT");
898 : : case PL_HOOK_OUTPUT: return pl_str0("OUTPUT");
899 : : };
900 : :
901 : 0 : pl_unreachable();
902 : : }
903 : :
904 : : struct hook_pass {
905 : : enum pl_hook_stage exec_stages;
906 : : struct custom_shader_hook hook;
907 : : };
908 : :
909 : : struct pass_tex {
910 : : pl_str name;
911 : : pl_tex tex;
912 : :
913 : : // Metadata
914 : : pl_rect2df rect;
915 : : struct pl_color_repr repr;
916 : : struct pl_color_space color;
917 : : int comps;
918 : : };
919 : :
920 : : struct hook_priv {
921 : : pl_log log;
922 : : pl_gpu gpu;
923 : : void *alloc;
924 : :
925 : : PL_ARRAY(struct hook_pass) hook_passes;
926 : : PL_ARRAY(struct pl_hook_par) hook_params;
927 : :
928 : : // Fixed (for shader-local resources)
929 : : PL_ARRAY(struct pl_shader_desc) descriptors;
930 : :
931 : : // Dynamic per pass
932 : : enum pl_hook_stage save_stages;
933 : : PL_ARRAY(struct pass_tex) pass_textures;
934 : : pl_shader trc_helper;
935 : :
936 : : // State for PRNG/frame count
937 : : int frame_count;
938 : : uint64_t prng_state[4];
939 : : };
940 : :
941 : 10 : static void hook_reset(void *priv)
942 : : {
943 : : struct hook_priv *p = priv;
944 : 10 : p->pass_textures.num = 0;
945 : 10 : }
946 : :
947 : : // Context during execution of a hook
948 : : struct hook_ctx {
949 : : struct hook_priv *priv;
950 : : const struct pl_hook_params *params;
951 : : struct pass_tex hooked;
952 : : };
953 : :
954 : 44 : static bool lookup_tex(struct hook_ctx *ctx, pl_str var, float size[2])
955 : : {
956 : 44 : struct hook_priv *p = ctx->priv;
957 : 44 : const struct pl_hook_params *params = ctx->params;
958 : :
959 [ + + ]: 44 : if (pl_str_equals0(var, "HOOKED")) {
960 [ - + ]: 36 : pl_assert(ctx->hooked.tex);
961 : 36 : size[0] = ctx->hooked.tex->params.w;
962 : 36 : size[1] = ctx->hooked.tex->params.h;
963 : 36 : return true;
964 : : }
965 : :
966 [ - + ]: 8 : if (pl_str_equals0(var, "NATIVE_CROPPED")) {
967 : 0 : size[0] = fabs(pl_rect_w(params->src_rect));
968 : 0 : size[1] = fabs(pl_rect_h(params->src_rect));
969 : 0 : return true;
970 : : }
971 : :
972 [ - + ]: 8 : if (pl_str_equals0(var, "OUTPUT")) {
973 : 0 : size[0] = abs(pl_rect_w(params->dst_rect));
974 : 0 : size[1] = abs(pl_rect_h(params->dst_rect));
975 : 0 : return true;
976 : : }
977 : :
978 [ - + ]: 8 : if (pl_str_equals0(var, "MAIN"))
979 : : var = pl_str0("MAINPRESUB");
980 : :
981 [ + - ]: 8 : for (int i = 0; i < p->pass_textures.num; i++) {
982 [ + - ]: 8 : if (pl_str_equals(var, p->pass_textures.elem[i].name)) {
983 : 8 : pl_tex tex = p->pass_textures.elem[i].tex;
984 : 8 : size[0] = tex->params.w;
985 : 8 : size[1] = tex->params.h;
986 : 8 : return true;
987 : : }
988 : : }
989 : :
990 : : return false;
991 : : }
992 : :
993 : 12 : static bool lookup_var(struct hook_ctx *ctx, pl_str var, float *val)
994 : : {
995 : 12 : struct hook_priv *p = ctx->priv;
996 [ + - ]: 40 : for (int i = 0; i < p->hook_params.num; i++) {
997 : 40 : const struct pl_hook_par *hp = &p->hook_params.elem[i];
998 [ + + ]: 40 : if (pl_str_equals0(var, hp->name)) {
999 [ + + - - ]: 8 : switch (hp->type) {
1000 : 4 : case PL_VAR_SINT: *val = hp->data->i; return true;
1001 : 4 : case PL_VAR_UINT: *val = hp->data->u; return true;
1002 : 0 : case PL_VAR_FLOAT: *val = hp->data->f; return true;
1003 : : case PL_VAR_INVALID:
1004 : : case PL_VAR_TYPE_COUNT:
1005 : : break;
1006 : : }
1007 : :
1008 : 0 : pl_unreachable();
1009 : : }
1010 : :
1011 [ + + ]: 32 : if (hp->names) {
1012 [ + - ]: 4 : for (int j = hp->minimum.i; j <= hp->maximum.i; j++) {
1013 [ + - ]: 4 : if (pl_str_equals0(var, hp->names[j])) {
1014 : 4 : *val = j;
1015 : 4 : return true;
1016 : : }
1017 : : }
1018 : : }
1019 : : }
1020 : :
1021 [ # # ]: 0 : PL_WARN(p, "Variable '%.*s' not found in RPN expression!", PL_STR_FMT(var));
1022 : 0 : return false;
1023 : : }
1024 : :
1025 : : // Returns whether successful. 'result' is left untouched on failure
1026 : 58 : static bool eval_shexpr(struct hook_ctx *ctx,
1027 : : const struct shexp expr[MAX_SHEXP_SIZE],
1028 : : float *result)
1029 : : {
1030 : 58 : struct hook_priv *p = ctx->priv;
1031 : 58 : float stack[MAX_SHEXP_SIZE] = {0};
1032 : : int idx = 0; // points to next element to push
1033 : :
1034 [ + - ]: 164 : for (int i = 0; i < MAX_SHEXP_SIZE; i++) {
1035 [ - + + - : 164 : switch (expr[i].tag) {
+ + + ]
1036 : 58 : case SHEXP_END:
1037 : 58 : goto done;
1038 : :
1039 : 26 : case SHEXP_CONST:
1040 : : // Since our SHEXPs are bound by MAX_SHEXP_SIZE, it should be
1041 : : // impossible to overflow the stack
1042 [ - + ]: 26 : assert(idx < MAX_SHEXP_SIZE);
1043 : 26 : stack[idx++] = expr[i].val.cval;
1044 : 26 : continue;
1045 : :
1046 : 0 : case SHEXP_OP1:
1047 [ # # ]: 0 : if (idx < 1) {
1048 : 0 : PL_WARN(p, "Stack underflow in RPN expression!");
1049 : 0 : return false;
1050 : : }
1051 : :
1052 [ # # ]: 0 : switch (expr[i].val.op) {
1053 [ # # ]: 0 : case SHEXP_OP_NOT: stack[idx-1] = !stack[idx-1]; break;
1054 : 0 : default: pl_unreachable();
1055 : : }
1056 : 0 : continue;
1057 : :
1058 : 24 : case SHEXP_OP2:
1059 [ - + ]: 24 : if (idx < 2) {
1060 : 0 : PL_WARN(p, "Stack underflow in RPN expression!");
1061 : 0 : return false;
1062 : : }
1063 : :
1064 : : // Pop the operands in reverse order
1065 : 24 : float op2 = stack[--idx];
1066 : 24 : float op1 = stack[--idx];
1067 : : float res = 0.0;
1068 [ - - - + : 24 : switch (expr[i].val.op) {
- - + + +
- ]
1069 : 0 : case SHEXP_OP_ADD: res = op1 + op2; break;
1070 : 0 : case SHEXP_OP_SUB: res = op1 - op2; break;
1071 : 8 : case SHEXP_OP_MUL: res = op1 * op2; break;
1072 : 0 : case SHEXP_OP_DIV: res = op1 / op2; break;
1073 : 0 : case SHEXP_OP_MOD: res = fmodf(op1, op2); break;
1074 [ + - ]: 4 : case SHEXP_OP_GT: res = op1 > op2; break;
1075 [ - + ]: 8 : case SHEXP_OP_LT: res = op1 < op2; break;
1076 [ - + ]: 4 : case SHEXP_OP_EQ: res = fabsf(op1 - op2) <= 1e-6 * fmaxf(op1, op2); break;
1077 : 0 : case SHEXP_OP_NOT: pl_unreachable();
1078 : : }
1079 : :
1080 [ - + ]: 24 : if (!isfinite(res)) {
1081 : 0 : PL_WARN(p, "Illegal operation in RPN expression!");
1082 : 0 : return false;
1083 : : }
1084 : :
1085 : 24 : stack[idx++] = res;
1086 : 24 : continue;
1087 : :
1088 : 44 : case SHEXP_TEX_W:
1089 : 44 : case SHEXP_TEX_H: {
1090 : 44 : pl_str name = expr[i].val.varname;
1091 : : float size[2];
1092 : :
1093 [ - + ]: 44 : if (!lookup_tex(ctx, name, size)) {
1094 [ # # ]: 0 : PL_WARN(p, "Variable '%.*s' not found in RPN expression!",
1095 : : PL_STR_FMT(name));
1096 : 0 : return false;
1097 : : }
1098 : :
1099 [ + + ]: 44 : stack[idx++] = (expr[i].tag == SHEXP_TEX_W) ? size[0] : size[1];
1100 : 44 : continue;
1101 : : }
1102 : :
1103 : 24 : case SHEXP_VAR: {
1104 : 12 : pl_str name = expr[i].val.varname;
1105 : : float val;
1106 [ - + ]: 12 : if (!lookup_var(ctx, name, &val))
1107 : 0 : return false;
1108 : 12 : stack[idx++] = val;
1109 : 12 : continue;
1110 : : }
1111 : : }
1112 : : }
1113 : :
1114 : 0 : done:
1115 : : // Return the single stack element
1116 [ - + ]: 58 : if (idx != 1) {
1117 : 0 : PL_WARN(p, "Malformed stack after RPN expression!");
1118 : 0 : return false;
1119 : : }
1120 : :
1121 : 58 : *result = stack[0];
1122 : 58 : return true;
1123 : : }
1124 : :
1125 : : static double prng_step(uint64_t s[4])
1126 : : {
1127 : 18 : const uint64_t result = s[0] + s[3];
1128 : 18 : const uint64_t t = s[1] << 17;
1129 : :
1130 : 18 : s[2] ^= s[0];
1131 : 18 : s[3] ^= s[1];
1132 : 18 : s[1] ^= s[2];
1133 : 18 : s[0] ^= s[3];
1134 : :
1135 : 18 : s[2] ^= t;
1136 : 18 : s[3] = (s[3] << 45) | (s[3] >> (64 - 45));
1137 : 18 : return (result >> 11) * 0x1.0p-53;
1138 : : }
1139 : :
1140 : 12 : static bool bind_pass_tex(pl_shader sh, pl_str name,
1141 : : const struct pass_tex *ptex,
1142 : : const pl_rect2df *rect,
1143 : : bool hooked, bool mainpresub)
1144 : : {
1145 : : ident_t id, pos, pt;
1146 : :
1147 : : // Compatibility with mpv texture binding semantics
1148 : 12 : id = sh_bind(sh, ptex->tex, PL_TEX_ADDRESS_CLAMP, PL_TEX_SAMPLE_LINEAR,
1149 : : "hook_tex", rect, &pos, &pt);
1150 [ + - ]: 12 : if (!id)
1151 : : return false;
1152 : :
1153 [ + - ]: 24 : GLSLH("#define %.*s_raw "$" \n", PL_STR_FMT(name), id);
1154 : 12 : GLSLH("#define %.*s_pos "$" \n", PL_STR_FMT(name), pos);
1155 : 12 : GLSLH("#define %.*s_map "$"_map \n", PL_STR_FMT(name), pos);
1156 : 12 : GLSLH("#define %.*s_size vec2(textureSize("$", 0)) \n", PL_STR_FMT(name), id);
1157 : 12 : GLSLH("#define %.*s_pt "$" \n", PL_STR_FMT(name), pt);
1158 : :
1159 : 12 : float off[2] = { ptex->rect.x0, ptex->rect.y0 };
1160 : 12 : GLSLH("#define %.*s_off "$" \n", PL_STR_FMT(name),
1161 : : sh_var(sh, (struct pl_shader_var) {
1162 : : .var = pl_var_vec2("offset"),
1163 : : .data = off,
1164 : : }));
1165 : :
1166 : 12 : struct pl_color_repr repr = ptex->repr;
1167 : 12 : ident_t scale = SH_FLOAT(pl_color_repr_normalize(&repr));
1168 : 12 : GLSLH("#define %.*s_mul "$" \n", PL_STR_FMT(name), scale);
1169 : :
1170 : : // Compatibility with mpv
1171 : 12 : GLSLH("#define %.*s_rot mat2(1.0, 0.0, 0.0, 1.0) \n", PL_STR_FMT(name));
1172 : :
1173 : : // Sampling function boilerplate
1174 : 12 : GLSLH("#define %.*s_tex(pos) ("$" * vec4(textureLod("$", pos, 0.0))) \n",
1175 : : PL_STR_FMT(name), scale, id);
1176 : 12 : GLSLH("#define %.*s_texOff(off) (%.*s_tex("$" + "$" * vec2(off))) \n",
1177 : : PL_STR_FMT(name), PL_STR_FMT(name), pos, pt);
1178 : :
1179 : 12 : bool can_gather = ptex->tex->params.format->gatherable;
1180 [ + + ]: 12 : if (can_gather) {
1181 : 6 : GLSLH("#define %.*s_gather(pos, c) ("$" * vec4(textureGather("$", pos, c))) \n",
1182 : : PL_STR_FMT(name), scale, id);
1183 : : }
1184 : :
1185 [ + + ]: 12 : if (hooked) {
1186 : 8 : GLSLH("#define HOOKED_raw %.*s_raw \n", PL_STR_FMT(name));
1187 : 8 : GLSLH("#define HOOKED_pos %.*s_pos \n", PL_STR_FMT(name));
1188 : 8 : GLSLH("#define HOOKED_size %.*s_size \n", PL_STR_FMT(name));
1189 : 8 : GLSLH("#define HOOKED_rot %.*s_rot \n", PL_STR_FMT(name));
1190 : 8 : GLSLH("#define HOOKED_off %.*s_off \n", PL_STR_FMT(name));
1191 : 8 : GLSLH("#define HOOKED_pt %.*s_pt \n", PL_STR_FMT(name));
1192 : 8 : GLSLH("#define HOOKED_map %.*s_map \n", PL_STR_FMT(name));
1193 : 8 : GLSLH("#define HOOKED_mul %.*s_mul \n", PL_STR_FMT(name));
1194 : 8 : GLSLH("#define HOOKED_tex %.*s_tex \n", PL_STR_FMT(name));
1195 : 8 : GLSLH("#define HOOKED_texOff %.*s_texOff \n", PL_STR_FMT(name));
1196 [ + + ]: 8 : if (can_gather)
1197 : 4 : GLSLH("#define HOOKED_gather %.*s_gather \n", PL_STR_FMT(name));
1198 : : }
1199 : :
1200 [ + + ]: 12 : if (mainpresub) {
1201 : 4 : GLSLH("#define MAIN_raw MAINPRESUB_raw \n");
1202 : 4 : GLSLH("#define MAIN_pos MAINPRESUB_pos \n");
1203 : 4 : GLSLH("#define MAIN_size MAINPRESUB_size \n");
1204 : 4 : GLSLH("#define MAIN_rot MAINPRESUB_rot \n");
1205 : 4 : GLSLH("#define MAIN_off MAINPRESUB_off \n");
1206 : 4 : GLSLH("#define MAIN_pt MAINPRESUB_pt \n");
1207 : 4 : GLSLH("#define MAIN_map MAINPRESUB_map \n");
1208 : 4 : GLSLH("#define MAIN_mul MAINPRESUB_mul \n");
1209 : 4 : GLSLH("#define MAIN_tex MAINPRESUB_tex \n");
1210 : 4 : GLSLH("#define MAIN_texOff MAINPRESUB_texOff \n");
1211 [ + + ]: 4 : if (can_gather)
1212 : 2 : GLSLH("#define MAIN_gather MAINPRESUB_gather \n");
1213 : : }
1214 : :
1215 : : return true;
1216 : : }
1217 : :
1218 : 26 : static void save_pass_tex(struct hook_priv *p, struct pass_tex ptex)
1219 : : {
1220 : :
1221 [ + + ]: 38 : for (int i = 0; i < p->pass_textures.num; i++) {
1222 [ + + ]: 20 : if (!pl_str_equals(p->pass_textures.elem[i].name, ptex.name))
1223 : : continue;
1224 : :
1225 : 8 : p->pass_textures.elem[i] = ptex;
1226 : 8 : return;
1227 : : }
1228 : :
1229 : : // No texture with this name yet, append new one
1230 [ + + - + : 18 : PL_ARRAY_APPEND(p->alloc, p->pass_textures, ptex);
- + ]
1231 : : }
1232 : :
1233 : 14 : static struct pl_hook_res hook_hook(void *priv, const struct pl_hook_params *params)
1234 : : {
1235 : : struct hook_priv *p = priv;
1236 : 14 : pl_str stage = pl_stage_to_mp(params->stage);
1237 : 14 : struct pl_hook_res res = {0};
1238 : :
1239 : 14 : pl_shader sh = NULL;
1240 : 14 : struct hook_ctx ctx = {
1241 : : .priv = p,
1242 : : .params = params,
1243 : : .hooked = {
1244 : : .name = stage,
1245 : 14 : .tex = params->tex,
1246 : : .rect = params->rect,
1247 : : .repr = params->repr,
1248 : : .color = params->color,
1249 : 14 : .comps = params->components,
1250 : : },
1251 : : };
1252 : :
1253 : : // Save the input texture if needed
1254 [ + + ]: 14 : if (p->save_stages & params->stage) {
1255 [ + - ]: 16 : PL_TRACE(p, "Saving input texture '%.*s' for binding",
1256 : : PL_STR_FMT(ctx.hooked.name));
1257 : 8 : save_pass_tex(p, ctx.hooked);
1258 : : }
1259 : :
1260 [ + + ]: 44 : for (int n = 0; n < p->hook_passes.num; n++) {
1261 : 30 : const struct hook_pass *pass = &p->hook_passes.elem[n];
1262 [ + + ]: 30 : if (!(pass->exec_stages & params->stage))
1263 : 12 : continue;
1264 : :
1265 : 22 : const struct custom_shader_hook *hook = &pass->hook;
1266 [ + - + - ]: 66 : PL_TRACE(p, "Executing hook pass %d on stage '%.*s': %.*s",
1267 : : n, PL_STR_FMT(stage), PL_STR_FMT(hook->pass_desc));
1268 : :
1269 : : // Test for execution condition
1270 : 22 : float run = 0;
1271 [ - + ]: 22 : if (!eval_shexpr(&ctx, hook->cond, &run))
1272 : 0 : goto error;
1273 : :
1274 [ + + ]: 22 : if (!run) {
1275 : 4 : PL_TRACE(p, "Skipping hook due to condition");
1276 : 4 : continue;
1277 : : }
1278 : :
1279 : : // Generate a new shader object
1280 : 18 : sh = pl_dispatch_begin(params->dispatch);
1281 : :
1282 : : // Bind all necessary input textures
1283 [ + - ]: 40 : for (int i = 0; i < PL_ARRAY_SIZE(hook->bind_tex); i++) {
1284 : 40 : pl_str texname = hook->bind_tex[i];
1285 [ + + ]: 40 : if (!texname.len)
1286 : : break;
1287 : :
1288 : : // Convenience alias, to allow writing shaders that are oblivious
1289 : : // of the exact stage they hooked. This simply translates to
1290 : : // whatever stage actually fired the hook.
1291 : : bool hooked = false, mainpresub = false;
1292 [ + + ]: 22 : if (pl_str_equals0(texname, "HOOKED")) {
1293 : : // Continue with binding this, under the new name
1294 : : texname = stage;
1295 : : hooked = true;
1296 : : }
1297 : :
1298 : : // Compatibility alias, because MAIN and MAINPRESUB mean the same
1299 : : // thing to libplacebo, but user shaders are still written as
1300 : : // though they can be different concepts.
1301 [ + - ]: 22 : if (pl_str_equals0(texname, "MAIN") ||
1302 [ + + ]: 22 : pl_str_equals0(texname, "MAINPRESUB"))
1303 : : {
1304 : : texname = pl_str0("MAINPRESUB");
1305 : : mainpresub = true;
1306 : : }
1307 : :
1308 [ + + ]: 32 : for (int j = 0; j < p->descriptors.num; j++) {
1309 [ + + ]: 20 : if (pl_str_equals0(texname, p->descriptors.elem[j].desc.name)) {
1310 : : // Directly bind this, no need to bother with all the
1311 : : // `bind_pass_tex` boilerplate
1312 : 10 : ident_t id = sh_desc(sh, p->descriptors.elem[j]);
1313 [ + - ]: 20 : GLSLH("#define %.*s "$" \n", PL_STR_FMT(texname), id);
1314 : :
1315 [ + + ]: 10 : if (p->descriptors.elem[j].desc.type == PL_DESC_SAMPLED_TEX) {
1316 : 4 : GLSLH("#define %.*s_tex(pos) (textureLod("$", pos, 0.0)) \n",
1317 : : PL_STR_FMT(texname), id);
1318 : : }
1319 : 10 : goto next_bind;
1320 : : }
1321 : : }
1322 : :
1323 [ + - ]: 16 : for (int j = 0; j < p->pass_textures.num; j++) {
1324 [ + + ]: 16 : if (pl_str_equals(texname, p->pass_textures.elem[j].name)) {
1325 : : // Note: We bind the whole texture, rather than
1326 : : // hooked.rect, because user shaders in general are not
1327 : : // designed to handle cropped input textures.
1328 : : const struct pass_tex *ptex = &p->pass_textures.elem[j];
1329 : 12 : pl_rect2df rect = {
1330 : 12 : 0, 0, ptex->tex->params.w, ptex->tex->params.h,
1331 : : };
1332 : :
1333 [ - + - - ]: 12 : if (hook->offset_align && pl_str_equals(texname, stage)) {
1334 : 0 : float sx = pl_rect_w(ctx.hooked.rect) / pl_rect_w(params->src_rect),
1335 : 0 : sy = pl_rect_h(ctx.hooked.rect) / pl_rect_h(params->src_rect),
1336 : 0 : ox = ctx.hooked.rect.x0 - sx * params->src_rect.x0,
1337 : 0 : oy = ctx.hooked.rect.y0 - sy * params->src_rect.y0;
1338 : :
1339 : 0 : PL_TRACE(p, "Aligning plane with ref: %f %f", ox, oy);
1340 : 0 : pl_rect2df_offset(&rect, ox, oy);
1341 : : }
1342 : :
1343 [ - + ]: 12 : if (!bind_pass_tex(sh, texname, &p->pass_textures.elem[j],
1344 : : &rect, hooked, mainpresub))
1345 : : {
1346 : 0 : goto error;
1347 : : }
1348 : 12 : goto next_bind;
1349 : : }
1350 : : }
1351 : :
1352 : : // If none of the above matched, this is an unknown texture name,
1353 : : // so silently ignore this pass to match the mpv behavior
1354 [ # # ]: 0 : PL_TRACE(p, "Skipping hook due to no texture named '%.*s'.",
1355 : : PL_STR_FMT(texname));
1356 : 0 : pl_dispatch_abort(params->dispatch, &sh);
1357 : 0 : goto next_pass;
1358 : :
1359 : 22 : next_bind: ; // outer 'continue'
1360 : : }
1361 : :
1362 : : // Set up the input variables
1363 : 18 : p->frame_count++;
1364 : 18 : GLSLH("#define frame "$" \n", sh_var(sh, (struct pl_shader_var) {
1365 : : .var = pl_var_int("frame"),
1366 : : .data = &p->frame_count,
1367 : : .dynamic = true,
1368 : : }));
1369 : :
1370 : 18 : float random = prng_step(p->prng_state);
1371 : 18 : GLSLH("#define random "$" \n", sh_var(sh, (struct pl_shader_var) {
1372 : : .var = pl_var_float("random"),
1373 : : .data = &random,
1374 : : .dynamic = true,
1375 : : }));
1376 : :
1377 : 18 : float src_size[2] = { pl_rect_w(params->src_rect), pl_rect_h(params->src_rect) };
1378 : 18 : GLSLH("#define input_size "$" \n", sh_var(sh, (struct pl_shader_var) {
1379 : : .var = pl_var_vec2("input_size"),
1380 : : .data = src_size,
1381 : : }));
1382 : :
1383 : 18 : float dst_size[2] = { pl_rect_w(params->dst_rect), pl_rect_h(params->dst_rect) };
1384 : 18 : GLSLH("#define target_size "$" \n", sh_var(sh, (struct pl_shader_var) {
1385 : : .var = pl_var_vec2("target_size"),
1386 : : .data = dst_size,
1387 : : }));
1388 : :
1389 : 18 : float tex_off[2] = { params->src_rect.x0, params->src_rect.y0 };
1390 : 18 : GLSLH("#define tex_offset "$" \n", sh_var(sh, (struct pl_shader_var) {
1391 : : .var = pl_var_vec2("tex_offset"),
1392 : : .data = tex_off,
1393 : : }));
1394 : :
1395 : : // Custom parameters
1396 [ + + ]: 50 : for (int i = 0; i < p->hook_params.num; i++) {
1397 : 32 : const struct pl_hook_par *hp = &p->hook_params.elem[i];
1398 [ + + + - : 32 : switch (hp->mode) {
- ]
1399 : : case PL_HOOK_PAR_VARIABLE:
1400 : : case PL_HOOK_PAR_DYNAMIC:
1401 : 8 : GLSLH("#define %s "$" \n", hp->name,
1402 : : sh_var(sh, (struct pl_shader_var) {
1403 : : .var = {
1404 : : .name = hp->name,
1405 : : .type = hp->type,
1406 : : .dim_v = 1,
1407 : : .dim_m = 1,
1408 : : .dim_a = 1,
1409 : : },
1410 : : .data = hp->data,
1411 : : .dynamic = hp->mode == PL_HOOK_PAR_DYNAMIC,
1412 : : }));
1413 : 8 : break;
1414 : :
1415 : : case PL_HOOK_PAR_CONSTANT:
1416 : 8 : GLSLH("#define %s "$" \n", hp->name,
1417 : : sh_const(sh, (struct pl_shader_const) {
1418 : : .name = hp->name,
1419 : : .type = hp->type,
1420 : : .data = hp->data,
1421 : : .compile_time = true,
1422 : : }));
1423 : 8 : break;
1424 : :
1425 : : case PL_HOOK_PAR_DEFINE:
1426 : 16 : GLSLH("#define %s %d \n", hp->name, hp->data->i);
1427 : 16 : break;
1428 : :
1429 : : case PL_HOOK_PAR_MODE_COUNT:
1430 : 0 : pl_unreachable();
1431 : : }
1432 : :
1433 [ + + ]: 32 : if (hp->names) {
1434 [ + + ]: 24 : for (int j = hp->minimum.i; j <= hp->maximum.i; j++)
1435 : 16 : GLSLH("#define %s %d \n", hp->names[j], j);
1436 : : }
1437 : : }
1438 : :
1439 : : // Helper sub-shaders
1440 : 18 : uint64_t sh_id = SH_PARAMS(sh).id;
1441 : 18 : pl_shader_reset(p->trc_helper, pl_shader_params(
1442 : : .id = ++sh_id,
1443 : : .gpu = p->gpu,
1444 : : ));
1445 : 18 : pl_shader_linearize(p->trc_helper, params->orig_color);
1446 : 18 : GLSLH("#define linearize "$" \n", sh_subpass(sh, p->trc_helper));
1447 : :
1448 : 18 : pl_shader_reset(p->trc_helper, pl_shader_params(
1449 : : .id = ++sh_id,
1450 : : .gpu = p->gpu,
1451 : : ));
1452 : 18 : pl_shader_delinearize(p->trc_helper, params->orig_color);
1453 : 18 : GLSLH("#define delinearize "$" \n", sh_subpass(sh, p->trc_helper));
1454 : :
1455 : : // Load and run the user shader itself
1456 : 18 : sh_append_str(sh, SH_BUF_HEADER, hook->pass_body);
1457 [ + - ]: 36 : sh_describef(sh, "%.*s", PL_STR_FMT(hook->pass_desc));
1458 : :
1459 : : // Resolve output size and create framebuffer
1460 : 18 : float out_size[2] = {0};
1461 [ + - - + ]: 36 : if (!eval_shexpr(&ctx, hook->width, &out_size[0]) ||
1462 : 18 : !eval_shexpr(&ctx, hook->height, &out_size[1]))
1463 : : {
1464 : 0 : goto error;
1465 : : }
1466 : :
1467 : 18 : int out_w = roundf(out_size[0]),
1468 : 18 : out_h = roundf(out_size[1]);
1469 : :
1470 [ - + ]: 18 : if (!sh_require(sh, PL_SHADER_SIG_NONE, out_w, out_h))
1471 : 0 : goto error;
1472 : :
1473 : : // Generate a new texture to store the render result
1474 : : pl_tex fbo;
1475 : 18 : fbo = params->get_tex(params->priv, out_w, out_h);
1476 [ - + ]: 18 : if (!fbo) {
1477 : 0 : PL_ERR(p, "Failed dispatching hook: `get_tex` callback failed?");
1478 : 0 : goto error;
1479 : : }
1480 : :
1481 : : bool ok;
1482 [ - + ]: 18 : if (hook->is_compute) {
1483 : :
1484 [ # # ]: 0 : if (!sh_try_compute(sh, hook->threads_w, hook->threads_h, false, 0) ||
1485 [ # # ]: 0 : !fbo->params.storable)
1486 : : {
1487 : 0 : PL_ERR(p, "Failed dispatching COMPUTE shader");
1488 : 0 : goto error;
1489 : : }
1490 : :
1491 : 0 : GLSLP("#define out_image "$" \n", sh_desc(sh, (struct pl_shader_desc) {
1492 : : .binding.object = fbo,
1493 : : .desc = {
1494 : : .name = "out_image",
1495 : : .type = PL_DESC_STORAGE_IMG,
1496 : : .access = PL_DESC_ACCESS_WRITEONLY,
1497 : : },
1498 : : }));
1499 : :
1500 : 0 : sh->output = PL_SHADER_SIG_NONE;
1501 : :
1502 : 0 : GLSL("hook(); \n");
1503 : 0 : ok = pl_dispatch_compute(params->dispatch, pl_dispatch_compute_params(
1504 : : .shader = &sh,
1505 : : .dispatch_size = {
1506 : : // Round up as many blocks as are needed to cover the image
1507 : : PL_DIV_UP(out_w, hook->block_w),
1508 : : PL_DIV_UP(out_h, hook->block_h),
1509 : : 1,
1510 : : },
1511 : : .width = out_w,
1512 : : .height = out_h,
1513 : : ));
1514 : :
1515 : : } else {
1516 : :
1517 : : // Default non-COMPUTE shaders to explicitly use fragment shaders
1518 : : // only, to avoid breaking things like fwidth()
1519 [ - + ]: 18 : sh->type = PL_DEF(sh->type, SH_FRAGMENT);
1520 : :
1521 : 18 : GLSL("vec4 color = hook(); \n");
1522 : 18 : ok = pl_dispatch_finish(params->dispatch, pl_dispatch_params(
1523 : : .shader = &sh,
1524 : : .target = fbo,
1525 : : ));
1526 : :
1527 : : }
1528 : :
1529 [ - + ]: 18 : if (!ok)
1530 : 0 : goto error;
1531 : :
1532 : 18 : float sx = (float) out_w / ctx.hooked.tex->params.w,
1533 : 18 : sy = (float) out_h / ctx.hooked.tex->params.h,
1534 : 18 : x0 = sx * ctx.hooked.rect.x0 + hook->offset[0],
1535 : 18 : y0 = sy * ctx.hooked.rect.y0 + hook->offset[1];
1536 : :
1537 : 18 : pl_rect2df new_rect = {
1538 : : x0,
1539 : : y0,
1540 : 18 : x0 + sx * pl_rect_w(ctx.hooked.rect),
1541 : 18 : y0 + sy * pl_rect_h(ctx.hooked.rect),
1542 : : };
1543 : :
1544 [ - + ]: 18 : if (hook->offset_align) {
1545 : 0 : float rx = pl_rect_w(new_rect) / pl_rect_w(params->src_rect),
1546 : 0 : ry = pl_rect_h(new_rect) / pl_rect_h(params->src_rect),
1547 : 0 : ox = rx * params->src_rect.x0 - sx * ctx.hooked.rect.x0,
1548 : 0 : oy = ry * params->src_rect.y0 - sy * ctx.hooked.rect.y0;
1549 : :
1550 : 0 : pl_rect2df_offset(&new_rect, ox, oy);
1551 : : }
1552 : :
1553 : : // Save the result of this shader invocation
1554 : 18 : struct pass_tex ptex = {
1555 [ + + ]: 18 : .name = hook->save_tex.len ? hook->save_tex : stage,
1556 : : .tex = fbo,
1557 : : .repr = ctx.hooked.repr,
1558 : : .color = ctx.hooked.color,
1559 [ + + ]: 18 : .comps = PL_DEF(hook->comps, ctx.hooked.comps),
1560 : : .rect = new_rect,
1561 : : };
1562 : :
1563 : : // It's assumed that users will correctly normalize the input
1564 : 18 : pl_color_repr_normalize(&ptex.repr);
1565 : :
1566 [ + - ]: 36 : PL_TRACE(p, "Saving output texture '%.*s' from hook execution on '%.*s'",
1567 : : PL_STR_FMT(ptex.name), PL_STR_FMT(stage));
1568 : :
1569 : 18 : save_pass_tex(p, ptex);
1570 : :
1571 : : // Update the result object, unless we saved to a different name
1572 [ + + ]: 18 : if (pl_str_equals(ptex.name, stage)) {
1573 : 14 : ctx.hooked = ptex;
1574 : 14 : res = (struct pl_hook_res) {
1575 : : .output = PL_HOOK_SIG_TEX,
1576 : : .tex = fbo,
1577 : : .repr = ptex.repr,
1578 : : .color = ptex.color,
1579 : 14 : .components = ptex.comps,
1580 : : .rect = new_rect,
1581 : : };
1582 : : }
1583 : :
1584 : 22 : next_pass: ;
1585 : : }
1586 : :
1587 : 14 : return res;
1588 : :
1589 : : error:
1590 : 0 : pl_dispatch_abort(params->dispatch, &sh);
1591 : 0 : return (struct pl_hook_res) { .failed = true };
1592 : : }
1593 : :
1594 : 10 : const struct pl_hook *pl_mpv_user_shader_parse(pl_gpu gpu,
1595 : : const char *shader_text,
1596 : : size_t shader_len)
1597 : : {
1598 [ - + ]: 10 : if (!shader_len)
1599 : : return NULL;
1600 : :
1601 : 10 : pl_str shader = { (uint8_t *) shader_text, shader_len };
1602 : :
1603 : 10 : struct pl_hook *hook = pl_zalloc_obj(NULL, hook, struct hook_priv);
1604 : 10 : struct hook_priv *p = PL_PRIV(hook);
1605 : :
1606 : 10 : *hook = (struct pl_hook) {
1607 : : .input = PL_HOOK_SIG_TEX,
1608 : : .priv = p,
1609 : : .reset = hook_reset,
1610 : : .hook = hook_hook,
1611 : : .signature = pl_str_hash(shader),
1612 : : };
1613 : :
1614 : 10 : *p = (struct hook_priv) {
1615 : 10 : .log = gpu->log,
1616 : : .gpu = gpu,
1617 : : .alloc = hook,
1618 : 10 : .trc_helper = pl_shader_alloc(gpu->log, NULL),
1619 : : .prng_state = {
1620 : : // Determined by fair die roll
1621 : : 0xb76d71f9443c228allu, 0x93a02092fc4807e8llu,
1622 : : 0x06d81748f838bd07llu, 0x9381ee129dddce6cllu,
1623 : : },
1624 : : };
1625 : :
1626 [ + - ]: 10 : shader = pl_strdup(hook, shader);
1627 : :
1628 : : // Skip all garbage (e.g. comments) before the first header
1629 : 10 : int pos = pl_str_find(shader, pl_str0("//!"));
1630 [ - + ]: 10 : if (pos < 0) {
1631 : 0 : PL_ERR(gpu, "Shader appears to contain no headers?");
1632 : 0 : goto error;
1633 : : }
1634 [ + - ]: 10 : shader = pl_str_drop(shader, pos);
1635 : :
1636 : : // Loop over the file
1637 [ + + ]: 58 : while (shader.len > 0)
1638 : : {
1639 : : // Peek at the first header to dispatch the right type
1640 [ + + ]: 48 : if (pl_str_startswith0(shader, "//!TEXTURE")) {
1641 : : struct pl_shader_desc sd;
1642 [ - + ]: 6 : if (!parse_tex(gpu, hook, &shader, &sd))
1643 : 0 : goto error;
1644 : :
1645 : 6 : PL_INFO(gpu, "Registering named texture '%s'", sd.desc.name);
1646 [ + - - - : 6 : PL_ARRAY_APPEND(hook, p->descriptors, sd);
- - ]
1647 : 6 : continue;
1648 : : }
1649 : :
1650 [ + + ]: 42 : if (pl_str_startswith0(shader, "//!BUFFER")) {
1651 : : struct pl_shader_desc sd;
1652 [ - + ]: 4 : if (!parse_buf(gpu, hook, &shader, &sd))
1653 : 0 : goto error;
1654 : :
1655 : 4 : PL_INFO(gpu, "Registering named buffer '%s'", sd.desc.name);
1656 [ - + - + : 4 : PL_ARRAY_APPEND(hook, p->descriptors, sd);
- + ]
1657 : 4 : continue;
1658 : : }
1659 : :
1660 [ + + ]: 38 : if (pl_str_startswith0(shader, "//!PARAM")) {
1661 : : struct pl_hook_par hp;
1662 [ - + ]: 16 : if (!parse_param(gpu->log, hook, &shader, &hp))
1663 : 0 : goto error;
1664 : :
1665 : 16 : PL_INFO(gpu, "Registering named parameter '%s'", hp.name);
1666 [ + + - + : 16 : PL_ARRAY_APPEND(hook, p->hook_params, hp);
- + ]
1667 : 16 : continue;
1668 : : }
1669 : :
1670 : : struct custom_shader_hook h;
1671 [ - + ]: 22 : if (!parse_hook(gpu->log, &shader, &h))
1672 : 0 : goto error;
1673 : :
1674 : 22 : struct hook_pass pass = {
1675 : : .exec_stages = 0,
1676 : : .hook = h,
1677 : : };
1678 : :
1679 [ + + ]: 374 : for (int i = 0; i < PL_ARRAY_SIZE(h.hook_tex); i++)
1680 : 352 : pass.exec_stages |= mp_stage_to_pl(h.hook_tex[i]);
1681 [ + + ]: 374 : for (int i = 0; i < PL_ARRAY_SIZE(h.bind_tex); i++) {
1682 : 352 : p->save_stages |= mp_stage_to_pl(h.bind_tex[i]);
1683 [ + + ]: 352 : if (pl_str_equals0(h.bind_tex[i], "HOOKED"))
1684 : 8 : p->save_stages |= pass.exec_stages;
1685 : : }
1686 : :
1687 : : // As an extra precaution, this avoids errors when trying to run
1688 : : // conditions against planes that were never hooked. As a sole
1689 : : // exception, OUTPUT is special because it's hard-coded to return the
1690 : : // dst_rect even before it was hooked. (This is an apparently
1691 : : // undocumented mpv quirk, but shaders rely on it in practice)
1692 : : enum pl_hook_stage rpn_stages = 0;
1693 [ + + ]: 726 : for (int i = 0; i < PL_ARRAY_SIZE(h.width); i++) {
1694 [ + + ]: 704 : if (h.width[i].tag == SHEXP_TEX_W || h.width[i].tag == SHEXP_TEX_H)
1695 : 22 : rpn_stages |= mp_stage_to_pl(h.width[i].val.varname);
1696 : : }
1697 [ + + ]: 726 : for (int i = 0; i < PL_ARRAY_SIZE(h.height); i++) {
1698 [ + + ]: 704 : if (h.height[i].tag == SHEXP_TEX_W || h.height[i].tag == SHEXP_TEX_H)
1699 : 22 : rpn_stages |= mp_stage_to_pl(h.height[i].val.varname);
1700 : : }
1701 [ + + ]: 726 : for (int i = 0; i < PL_ARRAY_SIZE(h.cond); i++) {
1702 [ + + ]: 704 : if (h.cond[i].tag == SHEXP_TEX_W || h.cond[i].tag == SHEXP_TEX_H)
1703 : 8 : rpn_stages |= mp_stage_to_pl(h.cond[i].val.varname);
1704 : : }
1705 : :
1706 : 22 : p->save_stages |= rpn_stages & ~PL_HOOK_OUTPUT;
1707 : :
1708 [ + - ]: 44 : PL_INFO(gpu, "Registering hook pass: %.*s", PL_STR_FMT(h.pass_desc));
1709 [ + + - + : 22 : PL_ARRAY_APPEND(hook, p->hook_passes, pass);
- + ]
1710 : : }
1711 : :
1712 : : // We need to hook on both the exec and save stages, so that we can keep
1713 : : // track of any textures we might need
1714 : 10 : hook->stages |= p->save_stages;
1715 [ + + ]: 32 : for (int i = 0; i < p->hook_passes.num; i++)
1716 : 22 : hook->stages |= p->hook_passes.elem[i].exec_stages;
1717 : :
1718 : 10 : hook->parameters = p->hook_params.elem;
1719 : 10 : hook->num_parameters = p->hook_params.num;
1720 : :
1721 : 10 : PL_MSG(gpu, PL_LOG_DEBUG, "Loaded user shader:");
1722 : 10 : pl_msg_source(gpu->log, PL_LOG_DEBUG, shader_text);
1723 : :
1724 : 10 : return hook;
1725 : :
1726 : 0 : error:
1727 : 0 : pl_mpv_user_shader_destroy((const struct pl_hook **) &hook);
1728 : 0 : PL_MSG(gpu, PL_LOG_ERR, "Failed to parse user shader:");
1729 : 0 : pl_msg_source(gpu->log, PL_LOG_ERR, shader_text);
1730 : 0 : pl_log_stack_trace(gpu->log, PL_LOG_ERR);
1731 : 0 : return NULL;
1732 : : }
1733 : :
1734 : 10 : void pl_mpv_user_shader_destroy(const struct pl_hook **hookp)
1735 : : {
1736 : 10 : const struct pl_hook *hook = *hookp;
1737 [ + - ]: 10 : if (!hook)
1738 : : return;
1739 : :
1740 : 10 : struct hook_priv *p = PL_PRIV(hook);
1741 [ + + ]: 20 : for (int i = 0; i < p->descriptors.num; i++) {
1742 [ + + - - ]: 10 : switch (p->descriptors.elem[i].desc.type) {
1743 : 4 : case PL_DESC_BUF_UNIFORM:
1744 : : case PL_DESC_BUF_STORAGE:
1745 : : case PL_DESC_BUF_TEXEL_UNIFORM:
1746 : : case PL_DESC_BUF_TEXEL_STORAGE: {
1747 : 4 : pl_buf buf = p->descriptors.elem[i].binding.object;
1748 : 4 : pl_buf_destroy(p->gpu, &buf);
1749 : : break;
1750 : : }
1751 : :
1752 : 6 : case PL_DESC_SAMPLED_TEX:
1753 : : case PL_DESC_STORAGE_IMG: {
1754 : 6 : pl_tex tex = p->descriptors.elem[i].binding.object;
1755 : 6 : pl_tex_destroy(p->gpu, &tex);
1756 : : break;
1757 : :
1758 : : case PL_DESC_INVALID:
1759 : : case PL_DESC_TYPE_COUNT:
1760 : 0 : pl_unreachable();
1761 : : }
1762 : : }
1763 : : }
1764 : :
1765 : 10 : pl_shader_free(&p->trc_helper);
1766 : 10 : pl_free((void *) hook);
1767 : 10 : *hookp = NULL;
1768 : : }
|