vo_opengl: implement NNEDI3 prescaler

Implement NNEDI3, a neural network based deinterlacer.

The shader is reimplemented in GLSL and supports both 8x4 and 8x6
sampling window now. This allows the shader to be licensed
under LGPL2.1 so that it can be used in mpv.

The current implementation supports uploading the NN weights (up to
51kb with placebo setting) in two different way, via uniform buffer
object or hard coding into shader source. UBO requires OpenGL 3.1,
which only guarantee 16kb per block. But I find that 64kb seems to be
a default setting for recent card/driver (which nnedi3 is targeting),
so I think we're fine here (with default nnedi3 setting the size of
weights is 9kb). Hard-coding into shader requires OpenGL 3.3, for the
"intBitsToFloat()" built-in function. This is necessary to precisely
represent these weights in GLSL. I tried several human readable
floating point number format (with really high precision as for
single precision float), but for some reason they are not working
nicely, bad pixels (with NaN value) could be produced with some
weights set.

We could also add support to upload these weights with texture, just
for compatibility reason (etc. upscaling a still image with a low end
graphics card). But as I tested, it's rather slow even with 1D
texture (we probably had to use 2D texture due to dimension size
limitation). Since there is always better choice to do NNEDI3
upscaling for still image (vapoursynth plugin), it's not implemented
in this commit. If this turns out to be a popular demand from the
user, it should be easy to add it later.

For those who wants to optimize the performance a bit further, the
bottleneck seems to be:
1. overhead to upload and access these weights, (in particular,
   the shader code will be regenerated for each frame, it's on CPU
   though).
2. "dot()" performance in the main loop.
3. "exp()" performance in the main loop, there are various fast
   implementation with some bit tricks (probably with the help of the
   intBitsToFloat function).

The code is tested with nvidia card and driver (355.11), on Linux.

Closes #2230
This commit is contained in:
Bin Jin 2015-10-28 01:37:55 +00:00 committed by wm4
parent 3f73d63523
commit 27dc834f37
11 changed files with 403 additions and 5 deletions

View File

@ -565,6 +565,13 @@ Available video output drivers are:
Some parameters can be tuned with ``superxbr-sharpness`` and
``superxbr-edge-strength`` options.
``nnedi3``
An artificial neural network based deinterlacer, which can be used
to upscale images.
Extremely slow and requires a recent mid or high end graphics card
to work smoothly (as of 2015).
Note that all the filters above are designed (or implemented) to process
luma plane only and probably won't work as intended for video in RGB
format.
@ -587,6 +594,29 @@ Available video output drivers are:
A value less than 1.0 will disable the check.
``nnedi3-neurons=<16|32|64|128>``
Specify the neurons for nnedi3 prescaling (defaults to be 32). The
rendering time is expected to be linear to the number of neurons.
``nnedi3-window=<8x4|8x6>``
Specify the size of local window for sampling in nnedi3 prescaling
(defaults to be ``8x4``). The ``8x6`` window produces sharper images,
but is also slower.
``nnedi3-upload=<ubo|shader>``
Specify how to upload the NN weights to GPU. Depending on the graphics
card, driver, shader compiler and nnedi3 settings, both method can be
faster or slower.
``ubo``
Upload these weights via uniform buffer objects. This is the
default. (requires OpenGL 3.1)
``shader``
Hard code all the weights into the shader source code. (requires
OpenGL 3.3)
``pre-shaders=<files>``, ``post-shaders=<files>``, ``scale-shader=<file>``
Custom GLSL fragment shaders.

View File

@ -104,6 +104,7 @@ static const struct gl_functions gl_functions[] = {
DEF_FN(AttachShader),
DEF_FN(BindAttribLocation),
DEF_FN(BindBuffer),
DEF_FN(BindBufferBase),
DEF_FN(BindTexture),
DEF_FN(BlendFuncSeparate),
DEF_FN(BufferData),
@ -315,6 +316,16 @@ static const struct gl_functions gl_functions[] = {
{0}
},
},
// uniform buffer object extensions, requires OpenGL 3.1.
{
.ver_core = 310,
.extension = "ARB_uniform_buffer_object",
.functions = (const struct gl_function[]) {
DEF_FN(GetUniformBlockIndex),
DEF_FN(UniformBlockBinding),
{0}
},
},
};
#undef FN_OFFS
@ -466,10 +477,10 @@ void mpgl_load_functions2(GL *gl, void *(*get_fn)(void *ctx, const char *n),
gl->glsl_version = 120;
if (gl->version >= 300)
gl->glsl_version = 130;
// Specifically needed for OSX (normally we request 3.0 contexts only, but
// OSX always creates 3.2 contexts when requesting a core context).
if (gl->version >= 320)
gl->glsl_version = 150;
if (gl->version >= 330)
gl->glsl_version = 330;
}
if (is_software_gl(gl)) {

View File

@ -192,6 +192,7 @@ struct GL {
void (GLAPIENTRY *GenBuffers)(GLsizei, GLuint *);
void (GLAPIENTRY *DeleteBuffers)(GLsizei, const GLuint *);
void (GLAPIENTRY *BindBuffer)(GLenum, GLuint);
void (GLAPIENTRY *BindBufferBase)(GLenum, GLuint, GLuint);
GLvoid * (GLAPIENTRY * MapBuffer)(GLenum, GLenum);
GLboolean (GLAPIENTRY *UnmapBuffer)(GLenum);
void (GLAPIENTRY *BufferData)(GLenum, intptr_t, const GLvoid *, GLenum);
@ -260,6 +261,9 @@ struct GL {
GLint (GLAPIENTRY *GetVideoSync)(GLuint *);
GLint (GLAPIENTRY *WaitVideoSync)(GLint, GLint, unsigned int *);
GLuint (GLAPIENTRY *GetUniformBlockIndex)(GLuint, const GLchar *);
void (GLAPIENTRY *UniformBlockBinding)(GLuint, GLuint, GLuint);
void (GLAPIENTRY *DebugMessageCallback)(MP_GLDEBUGPROC callback,
const void *userParam);

217
video/out/opengl/nnedi3.c Normal file
View File

@ -0,0 +1,217 @@
/*
* This file is part of mpv.
*
* mpv is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* mpv is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License along
* with mpv. If not, see <http://www.gnu.org/licenses/>.
*
* You can alternatively redistribute this file and/or
* modify it under the terms of the GNU Lesser General Public
* License as published by the Free Software Foundation; either
* version 2.1 of the License, or (at your option) any later version.
*/
#include "nnedi3.h"
#include <assert.h>
#include <stdint.h>
#include <float.h>
#include <libavutil/bswap.h>
#include "video.h"
#define GLSL(x) gl_sc_add(sc, #x "\n");
#define GLSLF(...) gl_sc_addf(sc, __VA_ARGS__)
#define GLSLH(x) gl_sc_hadd(sc, #x "\n");
#define GLSLHF(...) gl_sc_haddf(sc, __VA_ARGS__)
const struct nnedi3_opts nnedi3_opts_def = {
.neurons = 1,
.window = 0,
.upload = NNEDI3_UPLOAD_UBO,
};
#define OPT_BASE_STRUCT struct nnedi3_opts
const struct m_sub_options nnedi3_conf = {
.opts = (const m_option_t[]) {
OPT_CHOICE("neurons", neurons, 0,
({"16", 0},
{"32", 1},
{"64", 2},
{"128", 3})),
OPT_CHOICE("window", window, 0,
({"8x4", 0},
{"8x6", 1})),
OPT_CHOICE("upload", upload, 0,
({"ubo", NNEDI3_UPLOAD_UBO},
{"shader", NNEDI3_UPLOAD_SHADER})),
{0}
},
.size = sizeof(struct nnedi3_opts),
.defaults = &nnedi3_opts_def,
};
const static char nnedi3_weights[40320 * 4 + 1] =
#include "video/out/opengl/nnedi3_weights.inc"
;
const int nnedi3_weight_offsets[9] =
{0, 1088, 3264, 7616, 16320, 17920, 21120, 27520, 40320};
const int nnedi3_neurons[4] = {16, 32, 64, 128};
const int nnedi3_window_width[2] = {8, 8};
const int nnedi3_window_height[2] = {4, 6};
const float* get_nnedi3_weights(const struct nnedi3_opts *conf, int *size)
{
int idx = conf->window * 4 + conf->neurons;
const int offset = nnedi3_weight_offsets[idx];
*size = (nnedi3_weight_offsets[idx + 1] - offset) * 4;
return (const float*)(nnedi3_weights + offset * 4);
}
void pass_nnedi3(struct gl_shader_cache *sc, int planes, int tex_num,
int step, const struct nnedi3_opts *conf,
struct gl_transform *transform)
{
assert(0 <= step && step < 2);
if (!conf)
conf = &nnedi3_opts_def;
const int neurons = nnedi3_neurons[conf->neurons];
const int width = nnedi3_window_width[conf->window];
const int height = nnedi3_window_height[conf->window];
const int offset = nnedi3_weight_offsets[conf->window * 4 + conf->neurons];
const uint32_t *weights = (const int*)(nnedi3_weights + offset * 4);
GLSLF("// nnedi3 (tex %d, step %d, neurons %d, window %dx%d, mode %d)\n",
tex_num, step + 1, neurons, width, height, conf->upload);
// This is required since each row will be encoded into vec4s
assert(width % 4 == 0);
const int sample_count = width * height / 4;
if (conf->upload == NNEDI3_UPLOAD_UBO) {
char buf[32];
snprintf(buf, sizeof(buf), "vec4 weights[%d];",
neurons * (sample_count * 2 + 1));
gl_sc_uniform_buffer(sc, "NNEDI3_WEIGHTS", buf, 0);
} else if (conf->upload == NNEDI3_UPLOAD_SHADER) {
// Somehow necessary for hard coding approach.
GLSLH(#pragma optionNV(fastprecision on))
}
GLSLHF("float nnedi3(sampler2D tex, vec2 pos, vec2 tex_size, int plane) {\n");
if (step == 0) {
*transform = (struct gl_transform){{{1.0,0.0}, {0.0,2.0}}, {0.0,-0.5}};
GLSLH(if (fract(pos.y * tex_size.y) < 0.5)
return texture(tex, pos + vec2(0, 0.25) / tex_size)[plane];)
GLSLHF("#define GET(i, j) "
"(texture(tex, pos+vec2((i)-(%f),(j)-(%f)+0.25)/tex_size)[plane])\n",
width / 2.0 - 1, (height - 1) / 2.0);
} else {
*transform = (struct gl_transform){{{2.0,0.0}, {0.0,1.0}}, {-0.5,0.0}};
GLSLH(if (fract(pos.x * tex_size.x) < 0.5)
return texture(tex, pos + vec2(0.25, 0) / tex_size)[plane];)
GLSLHF("#define GET(i, j) "
"(texture(tex, pos+vec2((j)-(%f)+0.25,(i)-(%f))/tex_size)[plane])\n",
(height - 1) / 2.0, width / 2.0 - 1);
}
GLSLHF("vec4 samples[%d];\n", sample_count);
for (int y = 0; y < height; y++)
for (int x = 0; x < width; x += 4) {
GLSLHF("samples[%d] = vec4(GET(%d, %d), GET(%d, %d),"
"GET(%d, %d), GET(%d, %d));\n",
(y * width + x) / 4, x, y, x+1, y, x+2, y, x+3, y);
}
GLSLHF("float sum = 0, sumsq = 0;"
"for (int i = 0; i < %d; i++) {"
"sum += dot(samples[i], vec4(1.0));"
"sumsq += dot(samples[i], samples[i]);"
"}\n", sample_count);
GLSLHF("float mstd0 = sum / %d.0;\n"
"float mstd1 = sumsq / %d.0 - mstd0 * mstd0;\n"
"float mstd2 = mix(0, inversesqrt(mstd1), mstd1 >= %.12e);\n"
"mstd1 *= mstd2;\n",
width * height, width * height, FLT_EPSILON);
GLSLHF("float vsum = 0, wsum = 0, sum1, sum2;\n");
if (conf->upload == NNEDI3_UPLOAD_SHADER) {
GLSLH(#define T(x) intBitsToFloat(x))
GLSLH(#define W(i,w0,w1,w2,w3) dot(samples[i],vec4(T(w0),T(w1),T(w2),T(w3))))
GLSLHF("#define WS(w0,w1) "
"sum1 = exp(sum1 * mstd2 + T(w0));"
"sum2 = sum2 * mstd2 + T(w1);"
"wsum += sum1;"
"vsum += sum1*(sum2/(1+abs(sum2)));\n");
for (int n = 0; n < neurons; n++) {
const uint32_t *weights_ptr = weights + (sample_count * 2 + 1) * 4 * n;
for (int s = 0; s < 2; s++) {
GLSLHF("sum%d", s + 1);
for (int i = 0; i < sample_count; i++) {
GLSLHF("%cW(%d,%d,%d,%d,%d)", i == 0 ? '=' : '+', i,
(int)av_le2ne32(weights_ptr[0]),
(int)av_le2ne32(weights_ptr[1]),
(int)av_le2ne32(weights_ptr[2]),
(int)av_le2ne32(weights_ptr[3]));
weights_ptr += 4;
}
GLSLHF(";");
}
GLSLHF("WS(%d,%d);\n", (int)av_le2ne32(weights_ptr[0]),
(int)av_le2ne32(weights_ptr[1]));
}
} else if (conf->upload == NNEDI3_UPLOAD_UBO) {
GLSLH(int idx = 0;)
GLSLHF("for (int n = 0; n < %d; n++) {\n", neurons);
for (int s = 0; s < 2; s++) {
GLSLHF("sum%d = 0;\n"
"for (int i = 0; i < %d; i++) {"
"sum%d += dot(samples[i], weights[idx++]);"
"}\n",
s + 1, sample_count, s + 1);
}
GLSLH(sum1 = exp(sum1 * mstd2 + weights[idx][0]);
sum2 = sum2 * mstd2 + weights[idx++][1];
wsum += sum1;
vsum += sum1*(sum2/(1+abs(sum2)));)
GLSLHF("}\n");
}
GLSLH(return clamp(mstd0 + 5.0 * vsum / wsum * mstd1, 0, 1);)
GLSLHF("}\n"); // nnedi3
GLSL(vec4 color = vec4(1.0);)
for (int i = 0; i < planes; i++) {
GLSLF("color[%d] = nnedi3(texture%d, texcoord%d, texture_size%d, %d);\n",
i, tex_num, tex_num, tex_num, i);
}
}

47
video/out/opengl/nnedi3.h Normal file
View File

@ -0,0 +1,47 @@
/*
* This file is part of mpv.
*
* mpv is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* mpv is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License along
* with mpv. If not, see <http://www.gnu.org/licenses/>.
*
* You can alternatively redistribute this file and/or
* modify it under the terms of the GNU Lesser General Public
* License as published by the Free Software Foundation; either
* version 2.1 of the License, or (at your option) any later version.
*/
#ifndef MP_GL_NNEDI3_H
#define MP_GL_NNEDI3_H
#include "common.h"
#include "utils.h"
#define NNEDI3_UPLOAD_UBO 0
#define NNEDI3_UPLOAD_SHADER 1
struct nnedi3_opts {
int neurons;
int window;
int upload;
};
extern const struct nnedi3_opts nnedi3_opts_def;
extern const struct m_sub_options nnedi3_conf;
const float* get_nnedi3_weights(const struct nnedi3_opts *conf, int *size);
void pass_nnedi3(struct gl_shader_cache *sc, int planes, int tex_num,
int step, const struct nnedi3_opts *conf,
struct gl_transform *transform);
#endif

Binary file not shown.

View File

@ -482,6 +482,7 @@ enum uniform_type {
UT_i,
UT_f,
UT_m,
UT_buffer,
};
struct sc_uniform {
@ -493,6 +494,10 @@ struct sc_uniform {
union {
GLfloat f[9];
GLint i[4];
struct {
char* text;
GLint binding;
} buffer;
} v;
};
@ -535,8 +540,11 @@ void gl_sc_reset(struct gl_shader_cache *sc)
{
sc->text[0] = '\0';
sc->header_text[0] = '\0';
for (int n = 0; n < sc->num_uniforms; n++)
for (int n = 0; n < sc->num_uniforms; n++) {
talloc_free(sc->uniforms[n].name);
if (sc->uniforms[n].type == UT_buffer)
talloc_free(sc->uniforms[n].v.buffer.text);
}
sc->num_uniforms = 0;
}
@ -697,6 +705,15 @@ void gl_sc_uniform_mat3(struct gl_shader_cache *sc, char *name,
transpose3x3(&u->v.f[0]);
}
void gl_sc_uniform_buffer(struct gl_shader_cache *sc, char *name,
const char *text, int binding)
{
struct sc_uniform *u = find_uniform(sc, name);
u->type = UT_buffer;
u->v.buffer.text = talloc_strdup(sc, text);
u->v.buffer.binding = binding;
}
// This will call glBindAttribLocation() on the shader before it's linked
// (OpenGL requires this to happen before linking). Basically, it associates
// the input variable names with the fields in the vao.
@ -723,6 +740,11 @@ static const char *vao_glsl_type(const struct gl_vao_entry *e)
// Assumes program is current (gl->UseProgram(program)).
static void update_uniform(GL *gl, GLuint program, struct sc_uniform *u)
{
if (u->type == UT_buffer) {
GLuint idx = gl->GetUniformBlockIndex(program, u->name);
gl->UniformBlockBinding(program, idx, u->v.buffer.binding);
return;
}
GLint loc = gl->GetUniformLocation(program, u->name);
if (loc < 0)
return;
@ -885,7 +907,10 @@ void gl_sc_gen_shader_and_reset(struct gl_shader_cache *sc)
ADD(frag, "%s", frag_vaos);
for (int n = 0; n < sc->num_uniforms; n++) {
struct sc_uniform *u = &sc->uniforms[n];
ADD(frag, "uniform %s %s;\n", u->glsl_type, u->name);
if (u->type == UT_buffer)
ADD(frag, "uniform %s { %s };\n", u->name, u->v.buffer.text);
else
ADD(frag, "uniform %s %s;\n", u->glsl_type, u->name);
}
// custom shader header
if (sc->header_text[0]) {

View File

@ -139,6 +139,8 @@ void gl_sc_uniform_mat2(struct gl_shader_cache *sc, char *name,
bool transpose, GLfloat *v);
void gl_sc_uniform_mat3(struct gl_shader_cache *sc, char *name,
bool transpose, GLfloat *v);
void gl_sc_uniform_buffer(struct gl_shader_cache *sc, char *name,
const char *text, int binding);
void gl_sc_set_vao(struct gl_shader_cache *sc, struct gl_vao *vao);
void gl_sc_gen_shader_and_reset(struct gl_shader_cache *sc);
void gl_sc_reset(struct gl_shader_cache *sc);

View File

@ -39,6 +39,7 @@
#include "osd.h"
#include "stream/stream.h"
#include "superxbr.h"
#include "nnedi3.h"
#include "video_shaders.h"
#include "video/out/filter_kernels.h"
#include "video/out/aspect.h"
@ -156,6 +157,8 @@ struct gl_video {
GLuint dither_texture;
int dither_size;
GLuint nnedi3_weights_buffer;
struct mp_image_params real_image_params; // configured format
struct mp_image_params image_params; // texture format (mind hwdec case)
struct mp_imgfmt_desc image_desc;
@ -444,12 +447,16 @@ const struct m_sub_options gl_video_conf = {
OPT_FLAG("deband", deband, 0),
OPT_SUBSTRUCT("deband", deband_opts, deband_conf, 0),
OPT_FLOAT("sharpen", unsharp, 0),
OPT_CHOICE("prescale", prescale, 0, ({"none", 0}, {"superxbr", 1})),
OPT_CHOICE("prescale", prescale, 0,
({"none", 0},
{"superxbr", 1},
{"nnedi3", 2})),
OPT_INTRANGE("prescale-passes",
prescale_passes, 0, 1, MAX_PRESCALE_PASSES),
OPT_FLOATRANGE("prescale-downscaling-threshold",
prescale_downscaling_threshold, 0, 0.0, 32.0),
OPT_SUBSTRUCT("superxbr", superxbr_opts, superxbr_conf, 0),
OPT_SUBSTRUCT("nnedi3", nnedi3_opts, nnedi3_conf, 0),
OPT_REMOVED("approx-gamma", "this is always enabled now"),
OPT_REMOVED("cscale-down", "chroma is never downscaled"),
@ -597,6 +604,8 @@ static void uninit_rendering(struct gl_video *p)
gl->DeleteTextures(1, &p->dither_texture);
p->dither_texture = 0;
gl->DeleteBuffers(1, &p->nnedi3_weights_buffer);
fbotex_uninit(&p->chroma_merge_fbo);
fbotex_uninit(&p->chroma_deband_fbo);
fbotex_uninit(&p->indirect_fbo);
@ -1202,6 +1211,10 @@ static void pass_prescale(struct gl_video *p, int src_tex_num, int dst_tex_num,
pass_superxbr(p->sc, planes, tex_num, step,
p->opts.superxbr_opts, &transform);
break;
case 2:
pass_nnedi3(p->sc, planes, tex_num, step,
p->opts.nnedi3_opts, &transform);
break;
default:
abort();
}
@ -1230,6 +1243,27 @@ static bool pass_prescale_luma(struct gl_video *p, float tex_mul,
struct src_tex *prescaled_tex,
int *prescaled_planes)
{
if (p->opts.prescale == 2 &&
p->opts.nnedi3_opts->upload == NNEDI3_UPLOAD_UBO)
{
// nnedi3 are configured to use uniform buffer objects.
if (!p->nnedi3_weights_buffer) {
p->gl->GenBuffers(1, &p->nnedi3_weights_buffer);
p->gl->BindBufferBase(GL_UNIFORM_BUFFER, 0,
p->nnedi3_weights_buffer);
int weights_size;
const float *weights =
get_nnedi3_weights(p->opts.nnedi3_opts, &weights_size);
MP_VERBOSE(p, "Uploading NNEDI3 weights via uniform buffer (size=%d)\n",
weights_size);
// We don't know the endianness of GPU, just assume it's little
// endian.
p->gl->BufferData(GL_UNIFORM_BUFFER, weights_size, weights,
GL_STATIC_DRAW);
}
}
// number of passes to apply prescaler, can be zero.
int prescale_passes = get_prescale_passes(p);
@ -2384,6 +2418,22 @@ static void check_gl_features(struct gl_video *p)
p->opts.deband = 0;
MP_WARN(p, "Disabling debanding (GLSL version too old).\n");
}
if (p->opts.prescale == 2) {
if (p->opts.nnedi3_opts->upload == NNEDI3_UPLOAD_UBO) {
// Check features for uniform buffer objects.
if (!p->gl->GetUniformBlockIndex || !p->gl->UniformBlockBinding) {
MP_WARN(p, "Disabling NNEDI3 (OpenGL 3.1 required).\n");
p->opts.prescale = 0;
}
} else if (p->opts.nnedi3_opts->upload == NNEDI3_UPLOAD_SHADER) {
// Check features for hard coding approach.
if (p->gl->glsl_version < 330) {
MP_WARN(p, "Disabling NNEDI3 (OpenGL 3.3 required).\n");
p->opts.prescale = 0;
}
}
}
}
static void init_gl(struct gl_video *p)
@ -2708,6 +2758,7 @@ static void assign_options(struct gl_video_opts *dst, struct gl_video_opts *src)
talloc_free(dst->post_shaders);
talloc_free(dst->deband_opts);
talloc_free(dst->superxbr_opts);
talloc_free(dst->nnedi3_opts);
*dst = *src;
@ -2719,6 +2770,11 @@ static void assign_options(struct gl_video_opts *dst, struct gl_video_opts *src)
src->superxbr_opts);
}
if (src->nnedi3_opts) {
dst->nnedi3_opts = m_sub_options_copy(NULL, &nnedi3_conf,
src->nnedi3_opts);
}
for (int n = 0; n < 4; n++) {
dst->scaler[n].kernel.name =
(char *)handle_scaler_opt(dst->scaler[n].kernel.name, n == 3);

View File

@ -102,6 +102,7 @@ struct gl_video_opts {
int prescale_passes;
float prescale_downscaling_threshold;
struct superxbr_opts *superxbr_opts;
struct nnedi3_opts *nnedi3_opts;
};
extern const struct m_sub_options gl_video_conf;

View File

@ -57,6 +57,10 @@ def build(ctx):
source = "sub/osd_font.otf",
target = "sub/osd_font.h")
ctx.file2string(
source = "video/out/opengl/nnedi3_weights.bin",
target = "video/out/opengl/nnedi3_weights.inc")
lua_files = ["defaults.lua", "assdraw.lua", "options.lua", "osc.lua",
"ytdl_hook.lua"]
for fn in lua_files:
@ -324,6 +328,7 @@ def build(ctx):
( "video/out/opengl/hwdec_osx.c", "videotoolbox-gl" ),
( "video/out/opengl/hwdec_vdpau.c", "vdpau-gl-x11" ),
( "video/out/opengl/lcms.c", "gl" ),
( "video/out/opengl/nnedi3.c", "gl" ),
( "video/out/opengl/osd.c", "gl" ),
( "video/out/opengl/superxbr.c", "gl" ),
( "video/out/opengl/utils.c", "gl" ),