diff --git a/DOCS/man/options.rst b/DOCS/man/options.rst index bf72d90fc0..e824e04e0f 100644 --- a/DOCS/man/options.rst +++ b/DOCS/man/options.rst @@ -4239,24 +4239,59 @@ The following video options are currently all specific to ``--vo=opengl`` and ... - Each block of metadata, along with the non-metadata lines after it, defines - a single pass. Each pass can set the following metadata: + Each section of metadata, along with the non-metadata lines after it, + defines a single block. There are currently two types of blocks, HOOKs and + TEXTUREs. - DESC - User-friendly description of the pass. This is the name used when - representing this shader in the list of passes for property - `vo-passes`. + A ``TEXTURE`` block can set the following options: + + TEXTURE <name> (required) + The name of this texture. Hooks can then bind the texture under this + name using BIND. This must be the first option of the texture block. + + SIZE <width> [<height>] [<depth>] + The dimensions of the texture. The height and depth are optional. The + type of texture (1D, 2D or 3D) depends on the number of components + specified. + + COMPONENTS <n> + The number of components per texel contained in the texture. Defaults + to 1. + + FORMAT <spec> + The texture format for the samples. A valid texture specification is + the number of bits followed by a single letter which is either ``f`` + (for float), ``i`` (for uint) or ``u`` (for unorm), for example + ``32f``. Defaults to ``8i``. + + FILTER <LINEAR|NEAREST> + The min/magnification filter used when sampling from this texture. + + BORDER <CLAMP|REPEAT|MIRROR> + The border wrapping mode used when sampling from this texture. + + Following the metadata is a string of bytes in hexadecimal notation that + define the raw texture data, corresponding to the format specified by + `FORMAT`, on a single line with no extra whitespace. + + A ``HOOK`` block can set the following options: HOOK <name> (required) The texture which to hook into. May occur multiple times within a metadata block, up to a predetermined limit. See below for a list of hookable textures. + DESC <title> + User-friendly description of the pass. This is the name used when + representing this shader in the list of passes for property + `vo-passes`. + BIND <name> - Loads a texture and makes it available to the pass, and sets up macros - to enable accessing it. See below for a list of set macros. By default, - no textures are bound. The special name HOOKED can be used to refer to - the texture that triggered this pass. + Loads a texture (either coming from mpv or from a ``TEXTURE`` block) + and makes it available to the pass. When binding textures from mpv, + this will also set up macros to facilitate accessing it properly. See + below for a list. By default, no textures are bound. The special name + HOOKED can be used to refer to the texture that triggered this pass. SAVE <name> Gives the name of the texture to save the result of this pass into. By @@ -4280,14 +4315,14 @@ The following video options are currently all specific to ``--vo=opengl`` and hook point can still cause that hook point to be saved, which has some minor overhead) - OFFSET ox oy + OFFSET <ox> <oy> Indicates a pixel shift (offset) introduced by this pass. These pixel offsets will be accumulated and corrected during the next scaling pass (``cscale`` or ``scale``). The default values are 0 0 which correspond to no shift. Note that offsets are ignored when not overwriting the hooked texture. - COMPONENTS n + COMPONENTS <n> Specifies how many components of this pass's output are relevant and should be stored in the texture, up to 4 (rgba). By default, this value is equal to the number of components in HOOKED. @@ -4303,7 +4338,7 @@ The following video options are currently all specific to ``--vo=opengl`` and difference is the fact that you can use shared memory inside compute shaders. - Each bound texture (via ``BIND``) will make available the following + Each bound mpv texture (via ``BIND``) will make available the following definitions to that shader pass, where NAME is the name of the bound texture: diff --git a/misc/bstr.c b/misc/bstr.c index 8c47b447d4..09eb6af25b 100644 --- a/misc/bstr.c +++ b/misc/bstr.c @@ -454,3 +454,40 @@ struct bstr bstr_get_ext(struct bstr s) return (struct bstr){NULL, 0}; return bstr_splice(s, dotpos + 1, s.len); } + +static int h_to_i(unsigned char c) +{ + if (c >= '0' && c <= '9') + return c - '0'; + if (c >= 'a' && c <= 'f') + return c - 'a' + 10; + if (c >= 'A' && c <= 'F') + return c - 'A' + 10; + + return -1; // invalid char +} + +bool bstr_decode_hex(void *talloc_ctx, struct bstr hex, struct bstr *out) +{ + if (!out) + return false; + + char *arr = talloc_array(talloc_ctx, char, hex.len / 2); + int len = 0; + + while (hex.len >= 2) { + int a = h_to_i(hex.start[0]); + int b = h_to_i(hex.start[1]); + hex = bstr_splice(hex, 2, hex.len); + + if (a < 0 || b < 0) { + talloc_free(arr); + return false; + } + + arr[len++] = (a << 4) | b; + } + + *out = (struct bstr){ .start = arr, .len = len }; + return true; +} diff --git a/misc/bstr.h b/misc/bstr.h index 63865a4421..199f300ba1 100644 --- a/misc/bstr.h +++ b/misc/bstr.h @@ -80,6 +80,10 @@ double bstrtod(struct bstr str, struct bstr *rest); void bstr_lower(struct bstr str); int bstr_sscanf(struct bstr str, const char *format, ...); +// Decode a string containing hexadecimal data. All whitespace will be silently +// ignored. When successful, this allocates a new array to store the output. +bool bstr_decode_hex(void *talloc_ctx, struct bstr hex, struct bstr *out); + // Decode the UTF-8 code point at the start of the string, and return the // character. // After calling this function, *out_next will point to the next character. diff --git a/stream/stream_memory.c b/stream/stream_memory.c index b23ad82c16..e0d01ff2d8 100644 --- a/stream/stream_memory.c +++ b/stream/stream_memory.c @@ -55,32 +55,6 @@ static int control(stream_t *s, int cmd, void *arg) return STREAM_UNSUPPORTED; } -static int h_to_i(unsigned char c) -{ - if (c >= '0' && c <= '9') - return c - '0'; - if (c >= 'a' && c <= 'f') - return c - 'a' + 10; - if (c >= 'A' && c <= 'F') - return c - 'A' + 10; - return -1; -} - -static bool bstr_to_hex_inplace(bstr *h) -{ - if (h->len % 2) - return false; - for (int n = 0; n < h->len / 2; n++) { - int hi = h_to_i(h->start[n * 2 + 0]); - int lo = h_to_i(h->start[n * 2 + 1]); - if (hi < 0 || lo < 0) - return false; - h->start[n] = (hi << 4) | lo; - } - h->len /= 2; - return true; -} - static int open_f(stream_t *stream) { stream->fill_buffer = fill_buffer; @@ -100,7 +74,7 @@ static int open_f(stream_t *stream) bstr_eatstart0(&data, "memory://"); stream_control(stream, STREAM_CTRL_SET_CONTENTS, &data); - if (use_hex && !bstr_to_hex_inplace(&p->data)) { + if (use_hex && !bstr_decode_hex(stream, p->data, &p->data)) { MP_FATAL(stream, "Invalid data.\n"); return STREAM_ERROR; } diff --git a/video/out/opengl/user_shaders.c b/video/out/opengl/user_shaders.c index 5cfd89b5ef..1b6fb42ab1 100644 --- a/video/out/opengl/user_shaders.c +++ b/video/out/opengl/user_shaders.c @@ -19,6 +19,7 @@ #include "misc/ctype.h" #include "user_shaders.h" +#include "formats.h" static bool parse_rpn_szexpr(struct bstr line, struct szexp out[MAX_SZEXP_SIZE]) { @@ -158,13 +159,10 @@ done: return true; } -bool parse_user_shader_pass(struct mp_log *log, struct bstr *body, - struct gl_user_shader *out) +static bool parse_hook(struct mp_log *log, struct bstr *body, + struct gl_user_shader_hook *out) { - if (!body || !out || !body->start || body->len == 0) - return false; - - *out = (struct gl_user_shader){ + *out = (struct gl_user_shader_hook){ .pass_desc = bstr0("(unknown)"), .offset = identity_trans, .width = {{ SZEXP_VAR_W, { .varname = bstr0("HOOKED") }}}, @@ -175,14 +173,6 @@ bool parse_user_shader_pass(struct mp_log *log, struct bstr *body, int hook_idx = 0; int bind_idx = 0; - // Skip all garbage (e.g. comments) before the first header - int pos = bstr_find(*body, bstr0("//!")); - if (pos < 0) { - mp_warn(log, "Shader appears to contain no headers!\n"); - return false; - } - *body = bstr_cut(*body, pos); - // Parse all headers while (true) { struct bstr rest; @@ -295,3 +285,162 @@ bool parse_user_shader_pass(struct mp_log *log, struct bstr *body, return true; } + +static bool parse_tex(struct mp_log *log, struct bstr *body, + struct gl_user_shader_tex *out) +{ + *out = (struct gl_user_shader_tex){ + .name = bstr0("USER_TEX"), + .w = 1, .h = 1, .d = 1, + .components = 1, + .bytes = 1, + .mpgl_type = MPGL_TYPE_UINT, + .gl_filter = GL_LINEAR, + .gl_target = GL_TEXTURE_1D, + .gl_border = GL_CLAMP_TO_EDGE, + }; + + while (true) { + struct bstr rest; + struct bstr line = bstr_strip(bstr_getline(*body, &rest)); + + if (!bstr_eatstart0(&line, "//!")) + break; + + *body = rest; + + if (bstr_eatstart0(&line, "TEXTURE")) { + out->name = bstr_strip(line); + continue; + } + + if (bstr_eatstart0(&line, "SIZE")) { + int num = bstr_sscanf(line, "%d %d %d", &out->w, &out->h, &out->d); + if (num < 1 || num > 3 || out->w < 1 || out->h < 1 || out->d < 1) { + mp_err(log, "Error while parsing SIZE!\n"); + return false; + } + static GLenum tgt[] = {GL_TEXTURE_1D, GL_TEXTURE_2D, GL_TEXTURE_3D}; + out->gl_target = tgt[num - 1]; + continue; + } + + if (bstr_eatstart0(&line, "COMPONENTS")) { + if (bstr_sscanf(line, "%d", &out->components) != 1) { + mp_err(log, "Error while parsing COMPONENTS!\n"); + return false; + } + continue; + } + + if (bstr_eatstart0(&line, "FORMAT")) { + int bits; + char fmt; + if (bstr_sscanf(line, "%d%c", &bits, &fmt) != 2) { + mp_err(log, "Error while parsing FORMAT!\n"); + return false; + } + + out->bytes = bits / 8; + switch (fmt) { + case 'f': out->mpgl_type = MPGL_TYPE_FLOAT; break; + case 'i': out->mpgl_type = MPGL_TYPE_UINT; break; + case 'u': out->mpgl_type = MPGL_TYPE_UNORM; break; + default: + mp_err(log, "Unrecognized FORMAT description: '%c'!\n", fmt); + return false; + } + continue; + } + + if (bstr_eatstart0(&line, "FILTER")) { + line = bstr_strip(line); + if (bstr_equals0(line, "LINEAR")) { + out->gl_filter = GL_LINEAR; + } else if (bstr_equals0(line, "NEAREST")) { + out->gl_filter = GL_NEAREST; + } else { + mp_err(log, "Unrecognized FILTER: '%.*s'!\n", BSTR_P(line)); + return false; + } + continue; + } + + if (bstr_eatstart0(&line, "BORDER")) { + line = bstr_strip(line); + if (bstr_equals0(line, "CLAMP")) { + out->gl_border = GL_CLAMP_TO_EDGE; + } else if (bstr_equals0(line, "REPEAT")) { + out->gl_border = GL_REPEAT; + } else if (bstr_equals0(line, "MIRROR")) { + out->gl_border = GL_MIRRORED_REPEAT; + } else { + mp_err(log, "Unrecognized BORDER: '%.*s'!\n", BSTR_P(line)); + return false; + } + continue; + } + + mp_err(log, "Unrecognized command '%.*s'!\n", BSTR_P(line)); + return false; + } + + // Decode the rest of the section (up to the next //! marker) as raw hex + // data for the texture + struct bstr hexdata; + if (bstr_split_tok(*body, "//!", &hexdata, body)) { + // Make sure the magic line is part of the rest + body->start -= 3; + body->len += 3; + } + + struct bstr tex; + if (!bstr_decode_hex(NULL, bstr_strip(hexdata), &tex)) { + mp_err(log, "Error while parsing TEXTURE body: must be a valid " + "hexadecimal sequence, on a single line!\n"); + return false; + } + + int expected_len = out->w * out->h * out->d * out->components * out->bytes; + if (tex.len != expected_len) { + mp_err(log, "Shader TEXTURE size mismatch: got %zd bytes, expected %d!\n", + tex.len, expected_len); + talloc_free(tex.start); + return false; + } + + out->texdata = tex.start; + return true; +} + +void parse_user_shader(struct mp_log *log, struct bstr shader, void *priv, + bool (*dohook)(void *p, struct gl_user_shader_hook hook), + bool (*dotex)(void *p, struct gl_user_shader_tex tex)) +{ + if (!dohook || !dotex || !shader.len) + return; + + // Skip all garbage (e.g. comments) before the first header + int pos = bstr_find(shader, bstr0("//!")); + if (pos < 0) { + mp_warn(log, "Shader appears to contain no headers!\n"); + return; + } + shader = bstr_cut(shader, pos); + + // Loop over the file + while (shader.len > 0) + { + // Peek at the first header to dispatch the right type + if (bstr_startswith0(shader, "//!TEXTURE")) { + struct gl_user_shader_tex t; + if (!parse_tex(log, &shader, &t) || !dotex(priv, t)) + return; + continue; + } + + struct gl_user_shader_hook h; + if (!parse_hook(log, &shader, &h) || !dohook(priv, h)) + return; + } +} diff --git a/video/out/opengl/user_shaders.h b/video/out/opengl/user_shaders.h index 7192309c54..bb550de2b8 100644 --- a/video/out/opengl/user_shaders.h +++ b/video/out/opengl/user_shaders.h @@ -55,7 +55,7 @@ struct szexp { } val; }; -struct gl_user_shader { +struct gl_user_shader_hook { struct bstr pass_desc; struct bstr hook_tex[SHADER_MAX_HOOKS]; struct bstr bind_tex[SHADER_MAX_BINDS]; @@ -70,10 +70,25 @@ struct gl_user_shader { int compute_h; }; -// Parse the next shader pass from `body`. The `struct bstr` is modified by the -// function. Returns false if the end of the string was reached (or on error). -bool parse_user_shader_pass(struct mp_log *log, struct bstr *body, - struct gl_user_shader *out); +struct gl_user_shader_tex { + struct bstr name; + int w, h, d; + int components; + int bytes; + int mpgl_type; + GLenum gl_target; + GLenum gl_filter; + GLenum gl_border; + void *texdata; + // for video.c + GLenum gl_tex; +}; + +// Parse the next shader block from `body`. The callbacks are invoked on every +// valid shader block parsed. +void parse_user_shader(struct mp_log *log, struct bstr shader, void *priv, + bool (*dohook)(void *p, struct gl_user_shader_hook hook), + bool (*dotex)(void *p, struct gl_user_shader_tex tex)); // Evaluate a szexp, given a lookup function for named textures bool eval_szexpr(struct mp_log *log, void *priv, diff --git a/video/out/opengl/video.c b/video/out/opengl/video.c index 417c1b62b0..1a55d8393d 100644 --- a/video/out/opengl/video.c +++ b/video/out/opengl/video.c @@ -240,6 +240,12 @@ struct gl_video { struct fbotex vdpau_deinterleave_fbo[2]; GLuint hdr_peak_ssbo; + // user pass descriptions and textures + struct tex_hook tex_hooks[SHADER_MAX_PASSES]; + int tex_hook_num; + struct gl_user_shader_tex user_textures[SHADER_MAX_PASSES]; + int user_tex_num; + int surface_idx; int surface_now; int frames_drawn; @@ -274,9 +280,7 @@ struct gl_video { struct gl_timer *upload_timer; struct gl_timer *blit_timer; - // hooks and saved textures - struct tex_hook tex_hooks[SHADER_MAX_PASSES]; - int tex_hook_num; + // intermediate textures struct saved_tex saved_tex[SHADER_MAX_SAVED]; int saved_tex_num; struct fbotex hook_fbos[SHADER_MAX_SAVED]; @@ -503,7 +507,11 @@ static void gl_video_reset_hooks(struct gl_video *p) for (int i = 0; i < p->tex_hook_num; i++) talloc_free(p->tex_hooks[i].priv); + for (int i = 0; i < p->user_tex_num; i++) + p->gl->DeleteTextures(1, &p->user_textures[i].gl_tex); + p->tex_hook_num = 0; + p->user_tex_num = 0; } static inline int fbosurface_wrap(int id) @@ -1378,6 +1386,51 @@ static void saved_tex_store(struct gl_video *p, const char *name, }; } +static bool pass_hook_setup_binds(struct gl_video *p, const char *name, + struct img_tex tex, struct tex_hook *hook) +{ + for (int t = 0; t < TEXUNIT_VIDEO_NUM; t++) { + char *bind_name = (char *)hook->bind_tex[t]; + + if (!bind_name) + continue; + + // This is a special name that means "currently hooked texture" + if (strcmp(bind_name, "HOOKED") == 0) { + int id = pass_bind(p, tex); + hook_prelude(p, "HOOKED", id, tex); + hook_prelude(p, name, id, tex); + continue; + } + + // BIND can also be used to load user-defined textures, in which + // case we will directly load them as a uniform instead of + // generating the hook_prelude boilerplate + for (int u = 0; u < p->user_tex_num; u++) { + struct gl_user_shader_tex *utex = &p->user_textures[u]; + if (bstr_equals0(utex->name, bind_name)) { + gl_sc_uniform_tex(p->sc, bind_name, utex->gl_target, utex->gl_tex); + goto next_bind; + } + } + + struct img_tex bind_tex; + if (!saved_tex_find(p, bind_name, &bind_tex)) { + // Clean up texture bindings and move on to the next hook + MP_DBG(p, "Skipping hook on %s due to no texture named %s.\n", + name, bind_name); + p->pass_tex_num -= t; + return false; + } + + hook_prelude(p, bind_name, pass_bind(p, bind_tex), bind_tex); + +next_bind: ; + } + + return true; +} + // Process hooks for a plane, saving the result and returning a new img_tex // If 'trans' is NULL, the shader is forbidden from transforming tex static struct img_tex pass_hook(struct gl_video *p, const char *name, @@ -1407,32 +1460,8 @@ found: continue; } - // Bind all necessary textures and add them to the prelude - for (int t = 0; t < TEXUNIT_VIDEO_NUM; t++) { - const char *bind_name = hook->bind_tex[t]; - struct img_tex bind_tex; - - if (!bind_name) - continue; - - // This is a special name that means "currently hooked texture" - if (strcmp(bind_name, "HOOKED") == 0) { - int id = pass_bind(p, tex); - hook_prelude(p, "HOOKED", id, tex); - hook_prelude(p, name, id, tex); - continue; - } - - if (!saved_tex_find(p, bind_name, &bind_tex)) { - // Clean up texture bindings and move on to the next hook - MP_DBG(p, "Skipping hook on %s due to no texture named %s.\n", - name, bind_name); - p->pass_tex_num -= t; - goto next_hook; - } - - hook_prelude(p, bind_name, pass_bind(p, bind_tex), bind_tex); - } + if (!pass_hook_setup_binds(p, name, tex, hook)) + continue; // Run the actual hook. This generates a series of GLSL shader // instructions sufficient for drawing the hook's output @@ -1471,8 +1500,6 @@ found: } saved_tex_store(p, store_name, saved_tex); - -next_hook: ; } return tex; @@ -1825,13 +1852,15 @@ static bool img_tex_equiv(struct img_tex a, struct img_tex b) gl_transform_eq(a.transform, b.transform); } -static void pass_add_hook(struct gl_video *p, struct tex_hook hook) +static bool add_hook(struct gl_video *p, struct tex_hook hook) { if (p->tex_hook_num < SHADER_MAX_PASSES) { p->tex_hooks[p->tex_hook_num++] = hook; + return true; } else { MP_ERR(p, "Too many passes! Limit is %d.\n", SHADER_MAX_PASSES); talloc_free(hook.priv); + return false; } } @@ -1896,7 +1925,7 @@ static bool szexp_lookup(void *priv, struct bstr var, float size[2]) static bool user_hook_cond(struct gl_video *p, struct img_tex tex, void *priv) { - struct gl_user_shader *shader = priv; + struct gl_user_shader_hook *shader = priv; assert(shader); float res = false; @@ -1907,7 +1936,7 @@ static bool user_hook_cond(struct gl_video *p, struct img_tex tex, void *priv) static void user_hook(struct gl_video *p, struct img_tex tex, struct gl_transform *trans, void *priv) { - struct gl_user_shader *shader = priv; + struct gl_user_shader_hook *shader = priv; assert(shader); pass_describe(p, "user shader: %.*s (%s)", BSTR_P(shader->pass_desc), @@ -1928,33 +1957,91 @@ static void user_hook(struct gl_video *p, struct img_tex tex, gl_transform_trans(shader->offset, trans); } -static void pass_hook_user_shaders(struct gl_video *p, char **shaders) +static bool add_user_hook(void *priv, struct gl_user_shader_hook hook) +{ + struct gl_video *p = priv; + struct gl_user_shader_hook *copy = talloc_ptrtype(p, copy); + *copy = hook; + + struct tex_hook texhook = { + .save_tex = bstrdup0(copy, hook.save_tex), + .components = hook.components, + .hook = user_hook, + .cond = user_hook_cond, + .priv = copy, + }; + + for (int h = 0; h < SHADER_MAX_HOOKS; h++) + texhook.hook_tex[h] = bstrdup0(copy, hook.hook_tex[h]); + for (int h = 0; h < SHADER_MAX_BINDS; h++) + texhook.bind_tex[h] = bstrdup0(copy, hook.bind_tex[h]); + + return add_hook(p, texhook); +} + +static bool add_user_tex(void *priv, struct gl_user_shader_tex tex) +{ + struct gl_video *p = priv; + GL *gl = p->gl; + + if (p->user_tex_num == SHADER_MAX_PASSES) { + MP_ERR(p, "Too many textures! Limit is %d.\n", SHADER_MAX_PASSES); + goto err; + } + + const struct gl_format *format = gl_find_format(gl, tex.mpgl_type, + tex.gl_filter == GL_LINEAR ? F_TF : 0, tex.bytes, tex.components); + + if (!format) { + MP_ERR(p, "Could not satisfy format requirements for user " + "shader texture '%.*s'!\n", BSTR_P(tex.name)); + goto err; + } + + GLenum type = format->type, + ifmt = format->internal_format, + fmt = format->format; + + GLenum tgt = tex.gl_target; + gl->GenTextures(1, &tex.gl_tex); + gl->BindTexture(tgt, tex.gl_tex); + gl->PixelStorei(GL_UNPACK_ALIGNMENT, 1); + + if (tgt == GL_TEXTURE_3D) { + gl->TexImage3D(tgt, 0, ifmt, tex.w, tex.h, tex.d, 0, fmt, type, tex.texdata); + gl->TexParameteri(tgt, GL_TEXTURE_WRAP_S, tex.gl_border); + gl->TexParameteri(tgt, GL_TEXTURE_WRAP_T, tex.gl_border); + gl->TexParameteri(tgt, GL_TEXTURE_WRAP_R, tex.gl_border); + } else if (tgt == GL_TEXTURE_2D) { + gl->TexImage2D(tgt, 0, ifmt, tex.w, tex.h, 0, fmt, type, tex.texdata); + gl->TexParameteri(tgt, GL_TEXTURE_WRAP_S, tex.gl_border); + gl->TexParameteri(tgt, GL_TEXTURE_WRAP_T, tex.gl_border); + } else { + gl->TexImage1D(tgt, 0, ifmt, tex.w, 0, fmt, type, tex.texdata); + gl->TexParameteri(tgt, GL_TEXTURE_WRAP_S, tex.gl_border); + } + talloc_free(tex.texdata); + + gl->TexParameteri(tgt, GL_TEXTURE_MIN_FILTER, tex.gl_filter); + gl->TexParameteri(tgt, GL_TEXTURE_MAG_FILTER, tex.gl_filter); + gl->BindTexture(tgt, 0); + + p->user_textures[p->user_tex_num++] = tex; + return true; + +err: + talloc_free(tex.texdata); + return false; +} + +static void load_user_shaders(struct gl_video *p, char **shaders) { if (!shaders) return; for (int n = 0; shaders[n] != NULL; n++) { struct bstr file = load_cached_file(p, shaders[n]); - struct gl_user_shader out; - while (parse_user_shader_pass(p->log, &file, &out)) { - struct gl_user_shader *hook = talloc_ptrtype(p, hook); - *hook = out; - - struct tex_hook texhook = { - .save_tex = bstrdup0(hook, hook->save_tex), - .components = hook->components, - .hook = user_hook, - .cond = user_hook_cond, - .priv = hook, - }; - - for (int h = 0; h < SHADER_MAX_HOOKS; h++) - texhook.hook_tex[h] = bstrdup0(hook, hook->hook_tex[h]); - for (int h = 0; h < SHADER_MAX_BINDS; h++) - texhook.bind_tex[h] = bstrdup0(hook, hook->bind_tex[h]); - - pass_add_hook(p, texhook); - } + parse_user_shader(p->log, file, p, add_user_hook, add_user_tex); } } @@ -1963,7 +2050,7 @@ static void gl_video_setup_hooks(struct gl_video *p) gl_video_reset_hooks(p); if (p->opts.deband) { - pass_add_hook(p, (struct tex_hook) { + add_hook(p, (struct tex_hook) { .hook_tex = {"LUMA", "CHROMA", "RGB", "XYZ"}, .bind_tex = {"HOOKED"}, .hook = deband_hook, @@ -1971,14 +2058,14 @@ static void gl_video_setup_hooks(struct gl_video *p) } if (p->opts.unsharp != 0.0) { - pass_add_hook(p, (struct tex_hook) { + add_hook(p, (struct tex_hook) { .hook_tex = {"MAIN"}, .bind_tex = {"HOOKED"}, .hook = unsharp_hook, }); } - pass_hook_user_shaders(p, p->opts.user_shaders); + load_user_shaders(p, p->opts.user_shaders); } // sample from video textures, set "color" variable to yuv value