Implement smooth scaling for simple sprites

This commit is contained in:
Splendide Imaginarius 2024-03-22 04:30:36 +00:00
parent ef822d1806
commit 58d7ec8519
8 changed files with 235 additions and 14 deletions

View file

@ -96,8 +96,23 @@
// "smoothScalingDown": 0,
// Apply smooth interpolation when bitmaps
// are upscaled (same values as smoothScaling)
// (default: 0)
//
// "bitmapSmoothScaling": 0,
// Apply smooth interpolation when bitmaps
// are downscaled (same values as smoothScaling)
// (default: 0)
//
// "bitmapSmoothScalingDown": 0,
// Apply mipmap interpolation when game screen
// is downscaled (requires "smoothScalingDown": 1)
// or bitmaps are downscaled (requires
// "smoothScalingDown": 1 or "bitmapSmoothScalingDown": 1)
// (default: false)
//
// "smoothScalingMipmaps": false,

View file

@ -136,6 +136,8 @@ void Config::read(int argc, char *argv[]) {
{"fixedAspectRatio", true},
{"smoothScaling", 0},
{"smoothScalingDown", 0},
{"bitmapSmoothScaling", 0},
{"bitmapSmoothScalingDown", 0},
{"smoothScalingMipmaps", false},
{"bicubicSharpness", 100},
#ifdef MKXPZ_SSL
@ -272,6 +274,8 @@ try { exp } catch (...) {}
SET_OPT(fixedAspectRatio, boolean);
SET_OPT(smoothScaling, integer);
SET_OPT(smoothScalingDown, integer);
SET_OPT(bitmapSmoothScaling, integer);
SET_OPT(bitmapSmoothScalingDown, integer);
SET_OPT(smoothScalingMipmaps, boolean);
SET_OPT(bicubicSharpness, integer);
#ifdef MKXPZ_SSL

View file

@ -45,6 +45,8 @@ struct Config {
bool fixedAspectRatio;
int smoothScaling;
int smoothScalingDown;
int bitmapSmoothScaling;
int bitmapSmoothScalingDown;
bool smoothScalingMipmaps;
int bicubicSharpness;
#ifdef MKXPZ_SSL

View file

@ -337,7 +337,7 @@ struct BitmapPrivate
void bindTexture(ShaderBase &shader, bool substituteLoresSize = true)
{
if (selfHires) {
selfHires->bindTex(shader);
selfHires->bindTex(shader, substituteLoresSize);
return;
}
@ -2566,11 +2566,11 @@ bool Bitmap::getLooping() const
return p->animation.loop;
}
void Bitmap::bindTex(ShaderBase &shader)
void Bitmap::bindTex(ShaderBase &shader, bool substituteLoresSize)
{
// Hires mode is handled by p->bindTexture.
p->bindTexture(shader);
p->bindTexture(shader, substituteLoresSize);
}
void Bitmap::taintArea(const IntRect &rect)

View file

@ -158,7 +158,7 @@ public:
/* Binds the backing texture and sets the correct
* texture size uniform in shader */
void bindTex(ShaderBase &shader);
void bindTex(ShaderBase &shader, bool substituteLoresSize = true);
/* Adds 'rect' to tainted area */
void taintArea(const IntRect &rect);

View file

@ -385,6 +385,55 @@ void SimpleSpriteShader::setSpriteMat(const float value[16])
gl.UniformMatrix4fv(u_spriteMat, 1, GL_FALSE, value);
}
BicubicSpriteShader::BicubicSpriteShader()
{
INIT_SHADER(sprite, bicubic, BicubicSpriteShader);
ShaderBase::init();
GET_U(spriteMat);
GET_U(sourceSize);
GET_U(bc);
}
void BicubicSpriteShader::setSharpness(int sharpness)
{
gl.Uniform2f(u_bc, 1.f - sharpness * 0.01f, sharpness * 0.005f);
}
Lanczos3SpriteShader::Lanczos3SpriteShader()
{
INIT_SHADER(sprite, lanczos3, Lanczos3SpriteShader);
ShaderBase::init();
GET_U(spriteMat);
GET_U(sourceSize);
}
void Lanczos3SpriteShader::setTexSize(const Vec2i &value)
{
ShaderBase::setTexSize(value);
gl.Uniform2f(u_sourceSize, (float)value.x, (float)value.y);
}
#ifdef MKXPZ_SSL
XbrzSpriteShader::XbrzSpriteShader()
{
INIT_SHADER(sprite, xbrz, XbrzSpriteShader);
ShaderBase::init();
GET_U(spriteMat);
GET_U(sourceSize);
GET_U(targetScale);
}
void XbrzSpriteShader::setTargetScale(const Vec2 &value)
{
gl.Uniform2f(u_targetScale, value.x, value.y);
}
#endif
AlphaSpriteShader::AlphaSpriteShader()
{

View file

@ -137,7 +137,7 @@ public:
void setSpriteMat(const float value[16]);
private:
protected:
GLint u_spriteMat;
};
@ -365,6 +365,39 @@ protected:
};
#endif
class Lanczos3SpriteShader : public SimpleSpriteShader
{
public:
Lanczos3SpriteShader();
void setTexSize(const Vec2i &value);
protected:
GLint u_sourceSize;
};
class BicubicSpriteShader : public Lanczos3SpriteShader
{
public:
BicubicSpriteShader();
void setSharpness(int sharpness);
protected:
GLint u_bc;
};
class XbrzSpriteShader : public Lanczos3SpriteShader
{
public:
XbrzSpriteShader();
void setTargetScale(const Vec2 &value);
protected:
GLint u_targetScale;
};
/* Global object containing all available shaders */
struct ShaderSet
{
@ -390,6 +423,11 @@ struct ShaderSet
Lanczos3Shader lanczos3;
#ifdef MKXPZ_SSL
XbrzShader xbrz;
#endif
Lanczos3SpriteShader lanczos3Sprite;
BicubicSpriteShader bicubicSprite;
#ifdef MKXPZ_SSL
XbrzSpriteShader xbrzSprite;
#endif
};

View file

@ -24,6 +24,7 @@
#include "sharedstate.h"
#include "bitmap.h"
#include "debugwriter.h"
#include "config.h"
#include "etc.h"
#include "etc-internal.h"
#include "util.h"
@ -156,16 +157,34 @@ struct SpritePrivate
{
FloatRect rect = srcRect->toFloatRect();
Vec2i bmSize;
Vec2i bmSizeHires;
if (!nullOrDisposed(bitmap))
{
bmSize = Vec2i(bitmap->width(), bitmap->height());
if (bitmap->hasHires())
{
bmSizeHires = Vec2i(bitmap->getHires()->width(), bitmap->getHires()->height());
}
}
/* Clamp the rectangle so it doesn't reach outside
* the bitmap bounds */
rect.w = clamp<int>(rect.w, 0, bmSize.x-rect.x);
rect.h = clamp<int>(rect.h, 0, bmSize.y-rect.y);
quad.setTexRect(mirrored ? rect.hFlipped() : rect);
if (bmSizeHires.x && bmSizeHires.y && bmSize.x && bmSize.y)
{
FloatRect rectHires(rect.x * bmSizeHires.x / bmSize.x,
rect.y * bmSizeHires.y / bmSize.y,
rect.w * bmSizeHires.x / bmSize.x,
rect.h * bmSizeHires.y / bmSize.y);
quad.setTexRect(mirrored ? rectHires.hFlipped() : rectHires);
}
else
{
quad.setTexRect(mirrored ? rect.hFlipped() : rect);
}
quad.setPosRect(FloatRect(0, 0, rect.w, rect.h));
recomputeBushDepth();
@ -592,6 +611,8 @@ void Sprite::draw()
p->invert ||
(p->pattern && !p->pattern->isDisposed());
int scalingMethod = NearestNeighbor;
if (renderEffect)
{
SpriteShader &shader = shState->shaders().sprite;
@ -645,23 +666,115 @@ void Sprite::draw()
}
else
{
SimpleSpriteShader &shader = shState->shaders().simpleSprite;
shader.bind();
int sourceWidthHires = p->bitmap->hasHires() ? p->bitmap->getHires()->width() : p->bitmap->width();
int sourceHeightHires = p->bitmap->hasHires() ? p->bitmap->getHires()->height() : p->bitmap->height();
shader.setSpriteMat(p->trans.getMatrix());
shader.applyViewportProj();
base = &shader;
double framebufferScalingFactor = shState->config().enableHires ? shState->config().framebufferScalingFactor : 1.0;
int targetWidthHires = (int)lround(framebufferScalingFactor * p->bitmap->width() * p->trans.getScale().x);
int targetHeightHires = (int)lround(framebufferScalingFactor * p->bitmap->height() * p->trans.getScale().y);
int scaleIsSpecial = UpScale;
if (targetWidthHires == sourceWidthHires && targetHeightHires == sourceHeightHires)
{
scaleIsSpecial = SameScale;
}
if (targetWidthHires < sourceWidthHires && targetHeightHires < sourceHeightHires)
{
scaleIsSpecial = DownScale;
}
switch (scaleIsSpecial)
{
case SameScale:
scalingMethod = NearestNeighbor;
break;
case DownScale:
scalingMethod = shState->config().bitmapSmoothScalingDown;
break;
default:
scalingMethod = shState->config().bitmapSmoothScaling;
}
if (p->trans.getRotation() != 0.0)
{
scalingMethod = shState->config().bitmapSmoothScaling;
}
switch (scalingMethod)
{
case Bicubic:
{
BicubicSpriteShader &shader = shState->shaders().bicubicSprite;
shader.bind();
shader.setTexSize(Vec2i(sourceWidthHires, sourceHeightHires));
shader.setSharpness(shState->config().bicubicSharpness);
shader.setSpriteMat(p->trans.getMatrix());
shader.applyViewportProj();
base = &shader;
}
break;
case Lanczos3:
{
Lanczos3SpriteShader &shader = shState->shaders().lanczos3Sprite;
shader.bind();
shader.setTexSize(Vec2i(sourceWidthHires, sourceHeightHires));
shader.setSpriteMat(p->trans.getMatrix());
shader.applyViewportProj();
base = &shader;
}
break;
#ifdef MKXPZ_SSL
case xBRZ:
{
XbrzSpriteShader &shader = shState->shaders().xbrzSprite;
shader.bind();
shader.setTexSize(Vec2i(sourceWidthHires, sourceHeightHires));
shader.setTargetScale(Vec2((float)(shState->config().xbrzScalingFactor), (float)(shState->config().xbrzScalingFactor)));
shader.setSpriteMat(p->trans.getMatrix());
shader.applyViewportProj();
base = &shader;
}
break;
#endif
default:
{
SimpleSpriteShader &shader = shState->shaders().simpleSprite;
shader.bind();
shader.setSpriteMat(p->trans.getMatrix());
shader.applyViewportProj();
base = &shader;
}
}
}
glState.blendMode.pushSet(p->blendType);
p->bitmap->bindTex(*base);
p->bitmap->bindTex(*base, false);
#ifdef MKXPZ_SSL
if (scalingMethod == xBRZ)
{
XbrzShader &shader = shState->shaders().xbrz;
shader.setTargetScale(Vec2((float)(shState->config().xbrzScalingFactor), (float)(shState->config().xbrzScalingFactor)));
}
#endif
TEX::setSmooth(scalingMethod == Bilinear);
if (p->wave.active)
p->wave.qArray.draw();
else
p->quad.draw();
TEX::setSmooth(false);
glState.blendMode.pop();
}