diff options
Diffstat (limited to 'video')
-rw-r--r-- | video/out/opengl/user_shaders.c | 177 | ||||
-rw-r--r-- | video/out/opengl/user_shaders.h | 25 | ||||
-rw-r--r-- | video/out/opengl/video.c | 203 |
3 files changed, 328 insertions, 77 deletions
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 |