diff --git a/mkxp.json b/mkxp.json index 85d1efc..c6b470b 100644 --- a/mkxp.json +++ b/mkxp.json @@ -73,6 +73,13 @@ // "smoothScaling": false, + // Apply Lanczos3 interpolation when game screen + // is upscaled (typically higher quality than linear) + // (default: disabled) + // + // "lanczos3Scaling": false, + + // Sync screen redraws to the monitor refresh rate // (default: disabled) // diff --git a/shader/lanczos3.frag b/shader/lanczos3.frag new file mode 100644 index 0000000..df86365 --- /dev/null +++ b/shader/lanczos3.frag @@ -0,0 +1,48 @@ +// From https://raw.githubusercontent.com/Sentmoraap/doing-sdl-right/93a52a0db0ff2da5066cce12f5b9a2ac62e6f401/assets/lanczos3.frag +// Copyright 2020 Lilian Gimenez (Sentmoraap). +// MIT license. + +uniform sampler2D texture; +uniform vec2 sourceSize; +uniform vec2 texSizeInv; +varying vec2 v_texCoord; + +float lanczos3(float x) +{ + x = max(abs(x), 0.00001); + float val = x * 3.141592654; + return sin(val) * sin(val / 3) / (val * val); +} + +void main() +{ + vec2 pixel = v_texCoord * sourceSize + 0.5; + vec2 frac = fract(pixel); + vec2 onePixel = texSizeInv; + pixel = floor(pixel) * texSizeInv - onePixel / 2; + + float lanczosX[6]; + float sum = 0; + for(int x = 0; x < 6; x++) + { + lanczosX[x] = lanczos3(x - 2 - frac.x); + sum += lanczosX[x]; + } + for(int x = 0; x < 6; x++) lanczosX[x] /= sum; + sum = 0; + float lanczosY[6]; + for(int y = 0; y < 6; y++) + { + lanczosY[y] = lanczos3(y - 2 - frac.y); + sum += lanczosY[y]; + } + for(int y = 0; y < 6; y++) lanczosY[y] /= sum; + gl_FragColor = vec4(0); + for(int y = -2; y <= 3; y++) + { + vec4 colour = vec4(0); + for(int x = -2; x <= 3; x++) + colour += texture2D(texture, pixel + vec2(x * onePixel.x, y * onePixel.y)).rgba * lanczosX[x + 2]; + gl_FragColor += colour * lanczosY[y + 2]; + } +} diff --git a/shader/meson.build b/shader/meson.build index 1290460..4b5e249 100644 --- a/shader/meson.build +++ b/shader/meson.build @@ -15,6 +15,7 @@ embedded_shaders = [ 'simpleAlphaUni.frag', 'tilemap.frag', 'flashMap.frag', + 'lanczos3.frag', 'minimal.vert', 'simple.vert', 'simpleColor.vert', diff --git a/src/config.cpp b/src/config.cpp index befb9ba..79fc426 100644 --- a/src/config.cpp +++ b/src/config.cpp @@ -134,6 +134,7 @@ void Config::read(int argc, char *argv[]) { {"fullscreen", false}, {"fixedAspectRatio", true}, {"smoothScaling", false}, + {"lanczos3Scaling", false}, {"vsync", false}, {"defScreenW", 0}, {"defScreenH", 0}, @@ -257,6 +258,7 @@ try { exp } catch (...) {} SET_OPT(fullscreen, boolean); SET_OPT(fixedAspectRatio, boolean); SET_OPT(smoothScaling, boolean); + SET_OPT(lanczos3Scaling, boolean); SET_OPT(winResizable, boolean); SET_OPT(vsync, boolean); SET_STRINGOPT(windowTitle, windowTitle); diff --git a/src/config.h b/src/config.h index a98d6bf..3f8c9e9 100644 --- a/src/config.h +++ b/src/config.h @@ -43,6 +43,7 @@ struct Config { bool fullscreen; bool fixedAspectRatio; bool smoothScaling; + bool lanczos3Scaling; bool vsync; int defScreenW; diff --git a/src/display/gl/gl-meta.cpp b/src/display/gl/gl-meta.cpp index 8733449..ad491f4 100644 --- a/src/display/gl/gl-meta.cpp +++ b/src/display/gl/gl-meta.cpp @@ -24,6 +24,7 @@ #include "sharedstate.h" #include "glstate.h" #include "quad.h" +#include "config.h" namespace GLMeta { @@ -144,10 +145,22 @@ static void _blitBegin(FBO::ID fbo, const Vec2i &size) FBO::bind(fbo); glState.viewport.pushSet(IntRect(0, 0, size.x, size.y)); - SimpleShader &shader = shState->shaders().simple; - shader.bind(); - shader.applyViewportProj(); - shader.setTranslation(Vec2i()); + if (shState->config().lanczos3Scaling) + { + Lanczos3Shader &shader = shState->shaders().lanczos3; + shader.bind(); + shader.applyViewportProj(); + shader.setTranslation(Vec2i()); + shader.setTexSize(Vec2i(size.x, size.y)); + } + else + { + SimpleShader &shader = shState->shaders().simple; + shader.bind(); + shader.applyViewportProj(); + shader.setTranslation(Vec2i()); + shader.setTexSize(Vec2i(size.x, size.y)); + } } } @@ -169,8 +182,18 @@ void blitSource(TEXFBO &source) } else { - SimpleShader &shader = shState->shaders().simple; - shader.setTexSize(Vec2i(source.width, source.height)); + if (shState->config().lanczos3Scaling) + { + Lanczos3Shader &shader = shState->shaders().lanczos3; + shader.bind(); + shader.setTexSize(Vec2i(source.width, source.height)); + } + else + { + SimpleShader &shader = shState->shaders().simple; + shader.bind(); + shader.setTexSize(Vec2i(source.width, source.height)); + } TEX::bind(source.tex); } } diff --git a/src/display/gl/shader.cpp b/src/display/gl/shader.cpp index 0a0642c..d905a5b 100644 --- a/src/display/gl/shader.cpp +++ b/src/display/gl/shader.cpp @@ -44,6 +44,7 @@ #include "simpleAlphaUni.frag.xxd" #include "tilemap.frag.xxd" #include "flashMap.frag.xxd" +#include "lanczos3.frag.xxd" #include "minimal.vert.xxd" #include "simple.vert.xxd" #include "simpleColor.vert.xxd" @@ -746,3 +747,19 @@ void BltShader::setOpacity(float value) { gl.Uniform1f(u_opacity, value); } + +Lanczos3Shader::Lanczos3Shader() +{ + INIT_SHADER(simple, lanczos3, Lanczos3Shader); + + ShaderBase::init(); + + GET_U(texOffsetX); + GET_U(sourceSize); +} + +void Lanczos3Shader::setTexSize(const Vec2i &value) +{ + ShaderBase::setTexSize(value); + gl.Uniform2f(u_sourceSize, (float)value.x, (float)value.y); +} diff --git a/src/display/gl/shader.h b/src/display/gl/shader.h index 6024d21..b7a2881 100644 --- a/src/display/gl/shader.h +++ b/src/display/gl/shader.h @@ -113,7 +113,7 @@ public: void setTexOffsetX(int value); -private: +protected: GLint u_texOffsetX; }; @@ -326,6 +326,17 @@ private: GLint u_source, u_destination, u_subRect, u_opacity; }; +class Lanczos3Shader : public SimpleShader +{ +public: + Lanczos3Shader(); + + void setTexSize(const Vec2i &value); + +protected: + GLint u_sourceSize; +}; + /* Global object containing all available shaders */ struct ShaderSet { @@ -347,6 +358,7 @@ struct ShaderSet SimpleMatrixShader simpleMatrix; BlurShader blur; TilemapVXShader tilemapVX; + Lanczos3Shader lanczos3; }; #endif // SHADER_H