vo_opengl: add ewa_lanczos upscaler (aka jinc)

This is the polar (elliptic weighted average) version of lanczos.
This introduces a general new form of polar filters.
This commit is contained in:
Niklas Haas 2015-01-04 23:11:27 +01:00
parent 93e0d6f3b3
commit 26baf5b9da
No known key found for this signature in database
GPG Key ID: 3BA77D4BFDB10BCE
5 changed files with 158 additions and 30 deletions

View File

@ -304,6 +304,12 @@ Available video output drivers are:
``lanczos`` ``lanczos``
Generic Lanczos scaling filter. Set radius with ``lradius``. Generic Lanczos scaling filter. Set radius with ``lradius``.
``ewa_lanczos``
Generic elliptic weighted average Lanczos scaling filter. Also
known as Jinc. The radius can be set with ``lradius`` up to a
maximum value of 16, but note that performance drops very quickly
as the radius increases.
``spline36`` ``spline36``
This is the default when using ``opengl-hq``. This is the default when using ``opengl-hq``.
@ -348,7 +354,7 @@ Available video output drivers are:
Set radius for filters listed below, must be a float number between 1.0 Set radius for filters listed below, must be a float number between 1.0
and 8.0. Defaults to be 2.0 if not specified. and 8.0. Defaults to be 2.0 if not specified.
``sinc``, ``lanczos``, ``blackman``, ``gaussian`` ``sinc``, ``lanczos``, ``ewa_lanczos``, ``blackman``, ``gaussian``
Note that depending on filter implementation details and video scaling Note that depending on filter implementation details and video scaling
ratio, the radius that actually being used might be different ratio, the radius that actually being used might be different

View File

@ -58,6 +58,11 @@ bool mp_init_filter(struct filter_kernel *filter, const int *sizes,
{ {
if (filter->radius < 0) if (filter->radius < 0)
filter->radius = 2.0; filter->radius = 2.0;
// polar filters can be of any radius, and nothing special is needed
if (filter->polar) {
filter->size = filter->radius;
return true;
}
// only downscaling requires widening the filter // only downscaling requires widening the filter
filter->inv_scale = inv_scale >= 1.0 ? inv_scale : 1.0; filter->inv_scale = inv_scale >= 1.0 ? inv_scale : 1.0;
double support = filter->radius * filter->inv_scale; double support = filter->radius * filter->inv_scale;
@ -111,6 +116,18 @@ void mp_compute_lut(struct filter_kernel *filter, int count, float *out_array)
} }
} }
// Fill the given array with weights for the range [0, R], where R is the
// radius of hte filter. The array is interpreted as a one-dimensional array
// of count items.
void mp_compute_lut_polar(struct filter_kernel *filter, int count, float *out_array)
{
assert(filter->radius > 0);
for (int x = 0; x < count; x++) {
double r = x * filter->radius / (count - 1);
out_array[x] = r <= filter->radius ? filter->weight(filter, r) : 0;
}
}
typedef struct filter_kernel kernel; typedef struct filter_kernel kernel;
static double nearest(kernel *k, double x) static double nearest(kernel *k, double x)
@ -261,6 +278,14 @@ static double sinc(kernel *k, double x)
return sin(pix) / pix; return sin(pix) / pix;
} }
static double jinc(kernel *k, double x)
{
if (x == 0.0)
return 1.0;
double pix = M_PI * x;
return 2.0 * j1(pix) / pix;
}
static double lanczos(kernel *k, double x) static double lanczos(kernel *k, double x)
{ {
double radius = k->size / 2; double radius = k->size / 2;
@ -272,6 +297,48 @@ static double lanczos(kernel *k, double x)
return radius * sin(pix) * sin(pix / radius) / (pix * pix); return radius * sin(pix) * sin(pix / radius) / (pix * pix);
} }
static double ewa_lanczos(kernel *k, double x)
{
double radius = k->radius;
assert(radius >= 1.0);
// This is already three orders of magnitude slower than anything you could
// possibly hope to play back in realtime and results in tons of ringing
// artifacts, so I doubt anybody will complain.
if (radius > 16)
radius = 16;
if (fabs(x) < 1e-8)
return 1.0;
if (fabs(x) >= radius)
return 0.0;
// Precomputed zeros of the jinc() function, needed to adjust the
// window size. Computing this at runtime is nontrivial.
// Copied from: https://github.com/AviSynth/jinc-resize/blob/master/JincResize/JincFilter.cpp#L171
static double jinc_zeros[16] = {
1.2196698912665045,
2.2331305943815286,
3.2383154841662362,
4.2410628637960699,
5.2427643768701817,
6.2439216898644877,
7.2447598687199570,
8.2453949139520427,
9.2458926849494673,
10.246293348754916,
11.246622794877883,
12.246898461138105,
13.247132522181061,
14.247333735806849,
15.247508563037300,
16.247661874700962
};
double window = jinc_zeros[0] / jinc_zeros[(int)radius - 1];
return jinc(k, x) * jinc(k, x*window);
}
static double blackman(kernel *k, double x) static double blackman(kernel *k, double x)
{ {
double radius = k->size / 2; double radius = k->size / 2;
@ -303,6 +370,10 @@ const struct filter_kernel mp_filter_kernels[] = {
{"sinc3", 3, sinc}, {"sinc3", 3, sinc},
{"sinc4", 4, sinc}, {"sinc4", 4, sinc},
{"sinc", -1, sinc}, {"sinc", -1, sinc},
{"ewa_lanczos2", 2, ewa_lanczos, .polar = true},
{"ewa_lanczos3", 3, ewa_lanczos, .polar = true},
{"ewa_lanczos4", 4, ewa_lanczos, .polar = true},
{"ewa_lanczos", -1, ewa_lanczos, .polar = true},
{"lanczos2", 2, lanczos}, {"lanczos2", 2, lanczos},
{"lanczos3", 3, lanczos}, {"lanczos3", 3, lanczos},
{"lanczos4", 4, lanczos}, {"lanczos4", 4, lanczos},

View File

@ -28,6 +28,8 @@ struct filter_kernel {
// The filter params can be changed at runtime. Only used by some filters. // The filter params can be changed at runtime. Only used by some filters.
float params[2]; float params[2];
// Whether or not the filter uses polar coordinates
bool polar;
// The following values are set by mp_init_filter() at runtime. // The following values are set by mp_init_filter() at runtime.
// Number of coefficients; equals the rounded up radius multiplied with 2. // Number of coefficients; equals the rounded up radius multiplied with 2.
int size; int size;
@ -41,5 +43,6 @@ bool mp_init_filter(struct filter_kernel *filter, const int *sizes,
double scale); double scale);
void mp_compute_weights(struct filter_kernel *filter, double f, float *out_w); void mp_compute_weights(struct filter_kernel *filter, double f, float *out_w);
void mp_compute_lut(struct filter_kernel *filter, int count, float *out_array); void mp_compute_lut(struct filter_kernel *filter, int count, float *out_array);
void mp_compute_lut_polar(struct filter_kernel *filter, int count, float *out_array);
#endif /* MPLAYER_FILTER_KERNELS_H */ #endif /* MPLAYER_FILTER_KERNELS_H */

View File

@ -952,23 +952,29 @@ static void shader_setup_scaler(char **shader, struct scaler *scaler, int pass)
snprintf(name, sizeof(name), "sample_scaler%d", unit); snprintf(name, sizeof(name), "sample_scaler%d", unit);
APPENDF(shader, "#define DEF_SCALER%d \\\n ", unit); APPENDF(shader, "#define DEF_SCALER%d \\\n ", unit);
char lut_fn[40]; char lut_fn[40];
if (size == 2 || size == 6) { if (scaler->kernel->polar) {
snprintf(lut_fn, sizeof(lut_fn), "weights%d", size); // SAMPLE_CONVOLUTION_POLAR_R(NAME, R, LUT)
APPENDF(shader, "SAMPLE_CONVOLUTION_POLAR_R(%s, %d, %s)\n",
name, (int)scaler->kernel->radius, lut_tex);
} else { } else {
snprintf(lut_fn, sizeof(lut_fn), "weights_scaler%d", unit); if (size == 2 || size == 6) {
APPENDF(shader, "WEIGHTS_N(%s, %d) \\\n ", lut_fn, size); snprintf(lut_fn, sizeof(lut_fn), "weights%d", size);
} } else {
if (pass != -1) { snprintf(lut_fn, sizeof(lut_fn), "weights_scaler%d", unit);
// The direction/pass assignment is rather arbitrary, but fixed in APPENDF(shader, "WEIGHTS_N(%s, %d) \\\n ", lut_fn, size);
// other parts of the code (like FBO setup). }
const char *direction = pass == 0 ? "0, 1" : "1, 0"; if (pass != -1) {
// SAMPLE_CONVOLUTION_SEP_N(NAME, DIR, N, LUT, WEIGHTS_FUNC) // The direction/pass assignment is rather arbitrary, but fixed in
APPENDF(shader, "SAMPLE_CONVOLUTION_SEP_N(%s, vec2(%s), %d, %s, %s)\n", // other parts of the code (like FBO setup).
name, direction, size, lut_tex, lut_fn); const char *direction = pass == 0 ? "0, 1" : "1, 0";
} else { // SAMPLE_CONVOLUTION_SEP_N(NAME, DIR, N, LUT, WEIGHTS_FUNC)
// SAMPLE_CONVOLUTION_N(NAME, N, LUT, WEIGHTS_FUNC) APPENDF(shader, "SAMPLE_CONVOLUTION_SEP_N(%s, vec2(%s), %d, %s, %s)\n",
APPENDF(shader, "SAMPLE_CONVOLUTION_N(%s, %d, %s, %s)\n", name, direction, size, lut_tex, lut_fn);
name, size, lut_tex, lut_fn); } else {
// SAMPLE_CONVOLUTION_N(NAME, N, LUT, WEIGHTS_FUNC)
APPENDF(shader, "SAMPLE_CONVOLUTION_N(%s, %d, %s, %s)\n",
name, size, lut_tex, lut_fn);
}
} }
APPENDF(shader, "#define %s %s\n", target, name); APPENDF(shader, "#define %s %s\n", target, name);
} }
@ -1163,7 +1169,7 @@ static void compile_shaders(struct gl_video *p)
shader_def_opt(&header_final, "USE_DITHER", p->dither_texture != 0); shader_def_opt(&header_final, "USE_DITHER", p->dither_texture != 0);
shader_def_opt(&header_final, "USE_TEMPORAL_DITHER", p->opts.temporal_dither); shader_def_opt(&header_final, "USE_TEMPORAL_DITHER", p->opts.temporal_dither);
if (p->opts.scale_sep && p->scalers[0].kernel) { if (p->opts.scale_sep && p->scalers[0].kernel && !p->scalers[0].kernel->polar) {
header_sep = talloc_strdup(tmp, ""); header_sep = talloc_strdup(tmp, "");
shader_def_opt(&header_sep, "FIXED_SCALE", true); shader_def_opt(&header_sep, "FIXED_SCALE", true);
shader_setup_scaler(&header_sep, &p->scalers[0], 0); shader_setup_scaler(&header_sep, &p->scalers[0], 0);
@ -1312,32 +1318,53 @@ static void init_scaler(struct gl_video *p, struct scaler *scaler)
int size = scaler->kernel->size; int size = scaler->kernel->size;
int elems_per_pixel = 4; int elems_per_pixel = 4;
if (size == 2) { if (scaler->kernel->polar) {
elems_per_pixel = 1;
} else if (size == 2) {
elems_per_pixel = 2; elems_per_pixel = 2;
} else if (size == 6) { } else if (size == 6) {
elems_per_pixel = 3; elems_per_pixel = 3;
} }
int width = size / elems_per_pixel; int width = size / elems_per_pixel;
const struct fmt_entry *fmt = &gl_float16_formats[elems_per_pixel - 1]; const struct fmt_entry *fmt = &gl_float16_formats[elems_per_pixel - 1];
scaler->lut_name = scaler->index == 0 ? "lut_l" : "lut_c"; if (scaler->kernel->polar) {
scaler->lut_name = scaler->index == 0 ? "lut_polar_l" : "lut_polar_c";
} else {
scaler->lut_name = scaler->index == 0 ? "lut_l" : "lut_c";
}
gl->ActiveTexture(GL_TEXTURE0 + TEXUNIT_SCALERS + scaler->index); gl->ActiveTexture(GL_TEXTURE0 + TEXUNIT_SCALERS + scaler->index);
if (!scaler->gl_lut) if (!scaler->gl_lut)
gl->GenTextures(1, &scaler->gl_lut); gl->GenTextures(1, &scaler->gl_lut);
gl->BindTexture(GL_TEXTURE_2D, scaler->gl_lut); if (scaler->kernel->polar) {
gl->BindTexture(GL_TEXTURE_1D, scaler->gl_lut);
float *weights = talloc_array(NULL, float, LOOKUP_TEXTURE_SIZE * size); float *weights = talloc_array(NULL, float, LOOKUP_TEXTURE_SIZE);
mp_compute_lut(scaler->kernel, LOOKUP_TEXTURE_SIZE, weights); mp_compute_lut_polar(scaler->kernel, LOOKUP_TEXTURE_SIZE, weights);
gl->TexImage2D(GL_TEXTURE_2D, 0, fmt->internal_format, width, gl->TexImage1D(GL_TEXTURE_1D, 0, fmt->internal_format, LOOKUP_TEXTURE_SIZE,
LOOKUP_TEXTURE_SIZE, 0, fmt->format, GL_FLOAT, weights); 0, fmt->format, GL_FLOAT, weights);
talloc_free(weights); talloc_free(weights);
gl->TexParameteri(GL_TEXTURE_1D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
gl->TexParameteri(GL_TEXTURE_1D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
gl->TexParameteri(GL_TEXTURE_1D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE);
} else {
gl->BindTexture(GL_TEXTURE_2D, scaler->gl_lut);
float *weights = talloc_array(NULL, float, LOOKUP_TEXTURE_SIZE * size);
mp_compute_lut(scaler->kernel, LOOKUP_TEXTURE_SIZE, weights);
gl->TexImage2D(GL_TEXTURE_2D, 0, fmt->internal_format, width,
LOOKUP_TEXTURE_SIZE, 0, fmt->format, GL_FLOAT, weights);
talloc_free(weights);
gl->TexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
gl->TexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
gl->TexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE);
gl->TexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);
}
gl->TexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
gl->TexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
gl->TexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE);
gl->TexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);
gl->ActiveTexture(GL_TEXTURE0); gl->ActiveTexture(GL_TEXTURE0);

View File

@ -168,6 +168,8 @@ uniform vec2 chroma_center_offset;
uniform vec2 chroma_div; uniform vec2 chroma_div;
uniform sampler2D lut_c; uniform sampler2D lut_c;
uniform sampler2D lut_l; uniform sampler2D lut_l;
uniform sampler1D lut_polar_c;
uniform sampler1D lut_polar_l;
#if HAVE_3DTEX #if HAVE_3DTEX
uniform sampler3D lut_3d; uniform sampler3D lut_3d;
#endif #endif
@ -297,6 +299,25 @@ float[6] weights6(sampler2D lookup, float f) {
return res; \ return res; \
} }
#define SAMPLE_CONVOLUTION_POLAR_R(NAME, R, LUT) \
vec4 NAME(VIDEO_SAMPLER tex, vec2 texsize, vec2 texcoord) { \
vec2 pt = vec2(1.0) / texsize; \
vec2 fcoord = fract(texcoord * texsize - vec2(0.5)); \
vec2 base = texcoord - fcoord * pt; \
vec4 res = vec4(0); \
float wsum = 0; \
for (int y = 1-R; y <= R; y++) { \
for (int x = 1-R; x <= R; x++) { \
vec2 d = vec2(x,y) - fcoord; \
float w = texture1D(LUT, sqrt(d.x*d.x + d.y*d.y)/R).r; \
wsum += w; \
res += w * texture(tex, base + pt * vec2(x, y)); \
} \
} \
return res / wsum; \
}
#ifdef DEF_SCALER0 #ifdef DEF_SCALER0
DEF_SCALER0 DEF_SCALER0
#endif #endif