mirror of https://github.com/mpv-player/mpv
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:
parent
7c3d78fd82
commit
034faaa9d8
|
@ -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
|
||||||
|
|
12
misc/bstr.c
12
misc/bstr.c
|
@ -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++)
|
||||||
|
|
24
misc/bstr.h
24
misc/bstr.h
|
@ -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 : "")
|
||||||
|
|
||||||
|
|
|
@ -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;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -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;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
|
@ -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)
|
||||||
|
|
Loading…
Reference in New Issue