Skip to content

Commit

Permalink
vo_opengl: support loading custom user textures
Browse files Browse the repository at this point in the history
Parsing the texture data as raw strings makes the textures the most
portable and self-contained. In order to facilitate different types of
shaders, the parse_user_shader interaction has been changed to instead
have it loop through blocks and call the passed functions for each valid
block parsed. This is more modular and also cleaner, with better code
separation.

Closes mpv-player#4586.
  • Loading branch information
haasn committed Jul 12, 2017
1 parent d057e56 commit 6d401d2
Show file tree
Hide file tree
Showing 7 changed files with 412 additions and 116 deletions.
61 changes: 48 additions & 13 deletions DOCS/man/options.rst
Original file line number Diff line number Diff line change
Expand Up @@ -4219,24 +4219,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 <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`.
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
Expand All @@ -4260,19 +4295,19 @@ 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.

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:

Expand Down
37 changes: 37 additions & 0 deletions misc/bstr.c
Original file line number Diff line number Diff line change
Expand Up @@ -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;
}
4 changes: 4 additions & 0 deletions misc/bstr.h
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand Down
28 changes: 1 addition & 27 deletions stream/stream_memory.c
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand All @@ -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;
}
Expand Down
178 changes: 164 additions & 14 deletions video/out/opengl/user_shaders.c
Original file line number Diff line number Diff line change
Expand Up @@ -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])
{
Expand Down Expand Up @@ -158,13 +159,10 @@ bool eval_szexpr(struct mp_log *log, void *priv,
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") }}},
Expand All @@ -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;
Expand Down Expand Up @@ -287,3 +277,163 @@ 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,
void (*dohook)(void *p, struct gl_user_shader_hook hook),
void (*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))
return;
dotex(priv, t);
}

struct gl_user_shader_hook h;
if (!parse_hook(log, &shader, &h))
return;
dohook(priv, h);
}
}
Loading

0 comments on commit 6d401d2

Please sign in to comment.