vo_opengl: use RPN expressions for user hook sizes

This replaces the previous TRANSFORM by WIDTH, HEIGHT and OFFSET where
WIDTH and HEIGHT are RPN expressions. This allows for more fine-grained
control over the output size, and also makes sure that overwriting
existing textures works more cleanly.

(Also add some more useful bstr functions)
This commit is contained in:
Niklas Haas 2016-05-12 03:34:47 +02:00
parent 7c3d78fd82
commit 034faaa9d8
6 changed files with 238 additions and 24 deletions

View File

@ -739,11 +739,20 @@ Available video output drivers are:
into. By default, this is set to the special name HOOKED which has into. By default, this is set to the special name HOOKED which has
the effect of overwriting the hooked texture. the effect of overwriting the hooked texture.
TRANSFORM sx sy ox oy WIDTH <szexpr>, HEIGHT <szexpr>
Specifies how this pass intends to transform the hooked texture. Specifies the size of the resulting texture for this pass.
``sx``/``sy`` refer to a linear scale factor, and ``ox``/``oy`` ``szexpr`` refers to an expression in RPN (reverse polish
refer to a constant pixel shift that the shader will introduce. The notation), using the operators + - * /, floating point literals,
default values are 1 1 0 0 which leave the texture size unchanged. and references to existing texture sizes such as MAIN.width or
CHROMA.height. By default, these are set to HOOKED.w and HOOKED.h,
respectively.
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 Specifies how many components of this pass's output are relevant
@ -810,8 +819,9 @@ Available video output drivers are:
The final output image, after color management but before The final output image, after color management but before
dithering and drawing to screen. dithering and drawing to screen.
Only the textures labelled with (resizable) may be transformed by Only the textures labelled with ``resizable`` may be transformed by the
the pass. For all others, the TRANSFORM must be 1 1 0 0 (default). pass. When overwriting a texture marked ``fixed``, the WIDTH, HEIGHT
and OFFSET must be left at their default values.
``deband`` ``deband``
Enable the debanding algorithm. This greatly reduces the amount of Enable the debanding algorithm. This greatly reduces the amount of

View File

@ -215,9 +215,9 @@ struct bstr *bstr_splitlines(void *talloc_ctx, struct bstr str)
return r; return r;
} }
struct bstr bstr_getline(struct bstr str, struct bstr *rest) struct bstr bstr_splitchar(struct bstr str, struct bstr *rest, const char c)
{ {
int pos = bstrchr(str, '\n'); int pos = bstrchr(str, c);
if (pos < 0) if (pos < 0)
pos = str.len; pos = str.len;
if (rest) if (rest)
@ -243,6 +243,14 @@ bool bstr_eatstart(struct bstr *s, struct bstr prefix)
return true; return true;
} }
bool bstr_eatend(struct bstr *s, struct bstr prefix)
{
if (!bstr_endswith(*s, prefix))
return false;
s->len -= prefix.len;
return true;
}
void bstr_lower(struct bstr str) void bstr_lower(struct bstr str)
{ {
for (int i = 0; i < str.len; i++) for (int i = 0; i < str.len; i++)

View File

@ -116,10 +116,15 @@ int bstr_validate_utf8(struct bstr s);
// talloc, with talloc_ctx as parent. // talloc, with talloc_ctx as parent.
struct bstr bstr_sanitize_utf8_latin1(void *talloc_ctx, struct bstr s); struct bstr bstr_sanitize_utf8_latin1(void *talloc_ctx, struct bstr s);
// Return the text before the next line break, and return it. Change *rest to // Return the text before the occurance of a character, and return it. Change
// point to the text following this line break. (rest can be NULL.) // *rest to point to the text following this character. (rest can be NULL.)
// Line break characters are not stripped. struct bstr bstr_splitchar(struct bstr str, struct bstr *rest, const char c);
struct bstr bstr_getline(struct bstr str, struct bstr *rest);
// Like bstr_splitchar. Trailing newlines are not stripped.
static inline struct bstr bstr_getline(struct bstr str, struct bstr *rest)
{
return bstr_splitchar(str, rest, '\n');
}
// Strip one trailing line break. This is intended for use with bstr_getline, // Strip one trailing line break. This is intended for use with bstr_getline,
// and will remove the trailing \n or \r\n sequence. // and will remove the trailing \n or \r\n sequence.
@ -131,8 +136,10 @@ void bstr_xappend_asprintf(void *talloc_ctx, bstr *s, const char *fmt, ...)
void bstr_xappend_vasprintf(void *talloc_ctx, bstr *s, const char *fmt, va_list va) void bstr_xappend_vasprintf(void *talloc_ctx, bstr *s, const char *fmt, va_list va)
PRINTF_ATTRIBUTE(3, 0); PRINTF_ATTRIBUTE(3, 0);
// If s starts with prefix, return true and return the rest of the string in s. // If s starts/ends with prefix, return true and return the rest of the string
// in s.
bool bstr_eatstart(struct bstr *s, struct bstr prefix); bool bstr_eatstart(struct bstr *s, struct bstr prefix);
bool bstr_eatend(struct bstr *s, struct bstr prefix);
bool bstr_case_startswith(struct bstr s, struct bstr prefix); bool bstr_case_startswith(struct bstr s, struct bstr prefix);
bool bstr_case_endswith(struct bstr s, struct bstr suffix); bool bstr_case_endswith(struct bstr s, struct bstr suffix);
@ -200,11 +207,16 @@ static inline int bstr_find0(struct bstr haystack, const char *needle)
return bstr_find(haystack, bstr0(needle)); return bstr_find(haystack, bstr0(needle));
} }
static inline int bstr_eatstart0(struct bstr *s, const char *prefix) static inline bool bstr_eatstart0(struct bstr *s, const char *prefix)
{ {
return bstr_eatstart(s, bstr0(prefix)); return bstr_eatstart(s, bstr0(prefix));
} }
static inline bool bstr_eatend0(struct bstr *s, const char *prefix)
{
return bstr_eatend(s, bstr0(prefix));
}
// create a pair (not single value!) for "%.*s" printf syntax // create a pair (not single value!) for "%.*s" printf syntax
#define BSTR_P(bstr) (int)((bstr).len), ((bstr).start ? (char*)(bstr).start : "") #define BSTR_P(bstr) (int)((bstr).len), ((bstr).start ? (char*)(bstr).start : "")

View File

@ -16,6 +16,54 @@
*/ */
#include "user_shaders.h" #include "user_shaders.h"
#include "ctype.h"
static bool parse_rpn_szexpr(struct bstr line, struct szexp out[MAX_SZEXP_SIZE])
{
int pos = 0;
while (line.len > 0) {
struct bstr word = bstr_strip(bstr_splitchar(line, &line, ' '));
if (word.len == 0)
continue;
if (pos >= MAX_SZEXP_SIZE)
return false;
struct szexp *exp = &out[pos++];
if (bstr_eatend0(&word, ".w") || bstr_eatend0(&word, ".width")) {
exp->tag = SZEXP_VAR_W;
exp->val.varname = word;
continue;
}
if (bstr_eatend0(&word, ".h") || bstr_eatend0(&word, ".height")) {
exp->tag = SZEXP_VAR_H;
exp->val.varname = word;
continue;
}
switch (word.start[0]) {
case '+': exp->tag = SZEXP_OP2; exp->val.op = SZEXP_OP_ADD; continue;
case '-': exp->tag = SZEXP_OP2; exp->val.op = SZEXP_OP_SUB; continue;
case '*': exp->tag = SZEXP_OP2; exp->val.op = SZEXP_OP_MUL; continue;
case '/': exp->tag = SZEXP_OP2; exp->val.op = SZEXP_OP_DIV; continue;
}
if (isdigit(word.start[0])) {
exp->tag = SZEXP_CONST;
if (bstr_sscanf(word, "%f", &exp->val.cval) != 1)
return false;
continue;
}
// Some sort of illegal expression
return false;
}
return true;
}
// Returns false if no more shaders could be parsed // Returns false if no more shaders could be parsed
bool parse_user_shader_pass(struct mp_log *log, struct bstr *body, bool parse_user_shader_pass(struct mp_log *log, struct bstr *body,
@ -24,14 +72,19 @@ bool parse_user_shader_pass(struct mp_log *log, struct bstr *body,
if (!body || !out || !body->start || body->len == 0) if (!body || !out || !body->start || body->len == 0)
return false; return false;
*out = (struct gl_user_shader){ .transform = identity_trans }; *out = (struct gl_user_shader){
.offset = identity_trans,
.width = {{ SZEXP_VAR_W, { .varname = bstr0("HOOKED") }}},
.height = {{ SZEXP_VAR_H, { .varname = bstr0("HOOKED") }}},
};
int hook_idx = 0; int hook_idx = 0;
int bind_idx = 0; int bind_idx = 0;
// First parse all the headers // First parse all the headers
while (true) { while (true) {
struct bstr rest; struct bstr rest;
struct bstr line = bstr_getline(*body, &rest); struct bstr line = bstr_strip(bstr_getline(*body, &rest));
// Check for the presence of the magic line beginning // Check for the presence of the magic line beginning
if (!bstr_eatstart0(&line, "//!")) if (!bstr_eatstart0(&line, "//!"))
@ -65,13 +118,30 @@ bool parse_user_shader_pass(struct mp_log *log, struct bstr *body,
continue; continue;
} }
if (bstr_eatstart0(&line, "TRANSFORM")) { if (bstr_eatstart0(&line, "OFFSET")) {
float sx, sy, ox, oy; float ox, oy;
if (bstr_sscanf(line, "%f %f %f %f", &sx, &sy, &ox, &oy) != 4) { if (bstr_sscanf(line, "%f %f", &ox, &oy) != 2) {
mp_err(log, "Error while parsing TRANSFORM!\n"); mp_err(log, "Error while parsing OFFSET!\n");
return false;
}
out->offset.t[0] = ox;
out->offset.t[1] = oy;
continue;
}
if (bstr_eatstart0(&line, "WIDTH")) {
if (!parse_rpn_szexpr(line, out->width)) {
mp_err(log, "Error while parsing WIDTH!\n");
return false;
}
continue;
}
if (bstr_eatstart0(&line, "HEIGHT")) {
if (!parse_rpn_szexpr(line, out->height)) {
mp_err(log, "Error while parsing HEIGHT!\n");
return false; return false;
} }
out->transform = (struct gl_transform){{{sx, 0}, {0, sy}}, {ox, oy}};
continue; continue;
} }

View File

@ -24,13 +24,40 @@
#define SHADER_API 1 #define SHADER_API 1
#define SHADER_MAX_HOOKS 16 #define SHADER_MAX_HOOKS 16
#define SHADER_MAX_BINDS 6 #define SHADER_MAX_BINDS 6
#define MAX_SZEXP_SIZE 32
enum szexp_op {
SZEXP_OP_ADD,
SZEXP_OP_SUB,
SZEXP_OP_MUL,
SZEXP_OP_DIV,
};
enum szexp_tag {
SZEXP_END = 0, // End of an RPN expression
SZEXP_CONST, // Push a constant value onto the stack
SZEXP_VAR_W, // Get the width/height of a named texture (variable)
SZEXP_VAR_H,
SZEXP_OP2, // Pop two elements and push the result of a dyadic operation
} tag;
struct szexp {
enum szexp_tag tag;
union {
float cval;
struct bstr varname;
enum szexp_op op;
} val;
};
struct gl_user_shader { struct gl_user_shader {
struct bstr hook_tex[SHADER_MAX_HOOKS]; struct bstr hook_tex[SHADER_MAX_HOOKS];
struct bstr bind_tex[SHADER_MAX_BINDS]; struct bstr bind_tex[SHADER_MAX_BINDS];
struct bstr save_tex; struct bstr save_tex;
struct bstr pass_body; struct bstr pass_body;
struct gl_transform transform; struct gl_transform offset;
struct szexp width[MAX_SZEXP_SIZE];
struct szexp height[MAX_SZEXP_SIZE];
int components; int components;
}; };

View File

@ -1618,6 +1618,89 @@ static void user_hook_old(struct gl_video *p, struct img_tex tex,
GLSLF("color = %s(HOOKED, HOOKED_pos, HOOKED_size);\n", fn_name); GLSLF("color = %s(HOOKED, HOOKED_pos, HOOKED_size);\n", fn_name);
} }
// Returns 1.0 on failure to at least create a legal FBO
static float eval_szexpr(struct gl_video *p, struct img_tex tex,
struct szexp expr[MAX_SZEXP_SIZE])
{
float stack[MAX_SZEXP_SIZE] = {0};
int idx = 0; // points to next element to push
for (int i = 0; i < MAX_SZEXP_SIZE; i++) {
switch (expr[i].tag) {
case SZEXP_END:
goto done;
case SZEXP_CONST:
// Since our SZEXPs are bound by MAX_SZEXP_SIZE, it should be
// impossible to overflow the stack
assert(idx < MAX_SZEXP_SIZE);
stack[idx++] = expr[i].val.cval;
continue;
case SZEXP_OP2:
if (idx < 2) {
MP_WARN(p, "Stack underflow in RPN expression!\n");
return 1.0;
}
// Pop the operands in reverse order
float op2 = stack[--idx], op1 = stack[--idx], res = 0.0;
switch (expr[i].val.op) {
case SZEXP_OP_ADD: res = op1 + op2; break;
case SZEXP_OP_SUB: res = op1 - op2; break;
case SZEXP_OP_MUL: res = op1 * op2; break;
case SZEXP_OP_DIV: res = op1 / op2; break;
default: abort();
}
if (isnan(res)) {
MP_WARN(p, "Illegal operation in RPN expression!\n");
return 1.0;
}
stack[idx++] = res;
continue;
case SZEXP_VAR_W:
case SZEXP_VAR_H: {
struct bstr name = expr[i].val.varname;
struct img_tex var_tex;
// HOOKED is a special case
if (bstr_equals0(name, "HOOKED")) {
var_tex = tex;
goto found_tex;
}
for (int o = 0; o < p->saved_tex_num; o++) {
if (bstr_equals0(name, p->saved_tex[o].name)) {
var_tex = p->saved_tex[o].tex;
goto found_tex;
}
}
char *errname = bstrto0(NULL, name);
MP_WARN(p, "Texture %s not found in RPN expression!\n", errname);
talloc_free(errname);
return 1.0;
found_tex:
stack[idx++] = (expr[i].tag == SZEXP_VAR_W) ? var_tex.w : var_tex.h;
continue;
}
}
}
done:
// Return the single stack element
if (idx != 1) {
MP_WARN(p, "Malformed stack after RPN expression!\n");
return 1.0;
}
return stack[0];
}
static void user_hook(struct gl_video *p, struct img_tex tex, static void user_hook(struct gl_video *p, struct img_tex tex,
struct gl_transform *trans, void *priv) struct gl_transform *trans, void *priv)
{ {
@ -1628,7 +1711,11 @@ static void user_hook(struct gl_video *p, struct img_tex tex,
GLSLF("// custom hook\n"); GLSLF("// custom hook\n");
GLSLF("color = hook();\n"); GLSLF("color = hook();\n");
*trans = shader->transform; float w = eval_szexpr(p, tex, shader->width);
float h = eval_szexpr(p, tex, shader->height);
*trans = (struct gl_transform){{{w / tex.w, 0}, {0, h / tex.h}}};
gl_transform_trans(shader->offset, trans);
} }
static void user_hook_free(struct tex_hook *hook) static void user_hook_free(struct tex_hook *hook)