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
the effect of overwriting the hooked texture.
TRANSFORM sx sy ox oy
Specifies how this pass intends to transform the hooked texture.
``sx``/``sy`` refer to a linear scale factor, and ``ox``/``oy``
refer to a constant pixel shift that the shader will introduce. The
default values are 1 1 0 0 which leave the texture size unchanged.
WIDTH <szexpr>, HEIGHT <szexpr>
Specifies the size of the resulting texture for this pass.
``szexpr`` refers to an expression in RPN (reverse polish
notation), using the operators + - * /, floating point literals,
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
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
dithering and drawing to screen.
Only the textures labelled with (resizable) may be transformed by
the pass. For all others, the TRANSFORM must be 1 1 0 0 (default).
Only the textures labelled with ``resizable`` may be transformed by the
pass. When overwriting a texture marked ``fixed``, the WIDTH, HEIGHT
and OFFSET must be left at their default values.
``deband``
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;
}
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)
pos = str.len;
if (rest)
@ -243,6 +243,14 @@ bool bstr_eatstart(struct bstr *s, struct bstr prefix)
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)
{
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.
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
// point to the text following this line break. (rest can be NULL.)
// Line break characters are not stripped.
struct bstr bstr_getline(struct bstr str, struct bstr *rest);
// Return the text before the occurance of a character, and return it. Change
// *rest to point to the text following this character. (rest can be NULL.)
struct bstr bstr_splitchar(struct bstr str, struct bstr *rest, const char c);
// 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,
// 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)
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_eatend(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);
@ -200,11 +207,16 @@ static inline int bstr_find0(struct bstr haystack, const char *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));
}
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
#define BSTR_P(bstr) (int)((bstr).len), ((bstr).start ? (char*)(bstr).start : "")

View File

@ -16,6 +16,54 @@
*/
#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
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)
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 bind_idx = 0;
// First parse all the headers
while (true) {
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
if (!bstr_eatstart0(&line, "//!"))
@ -65,13 +118,30 @@ bool parse_user_shader_pass(struct mp_log *log, struct bstr *body,
continue;
}
if (bstr_eatstart0(&line, "TRANSFORM")) {
float sx, sy, ox, oy;
if (bstr_sscanf(line, "%f %f %f %f", &sx, &sy, &ox, &oy) != 4) {
mp_err(log, "Error while parsing TRANSFORM!\n");
if (bstr_eatstart0(&line, "OFFSET")) {
float ox, oy;
if (bstr_sscanf(line, "%f %f", &ox, &oy) != 2) {
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;
}
out->transform = (struct gl_transform){{{sx, 0}, {0, sy}}, {ox, oy}};
continue;
}

View File

@ -24,13 +24,40 @@
#define SHADER_API 1
#define SHADER_MAX_HOOKS 16
#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 bstr hook_tex[SHADER_MAX_HOOKS];
struct bstr bind_tex[SHADER_MAX_BINDS];
struct bstr save_tex;
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;
};

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);
}
// 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,
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("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)