ScaleNx - Artifact Removal and Algorithm Improvement

ScaleFx is the best smoothing shader, period.

Two hypothetical questions that yall might not have an answer for:

1.) Do you think this shader would be too complicated for the Super Nt?

2.) Would it be possible to create a shader that applies ScaleFx to dark luminance colors only? (ie leaving bright perceived-luminance pixels touching other bright pixels unsmoothed). I’ve wanted to see a half-smoothed, half-pixelated shader for ages.

Thank you for entertaining my hypothetical questions :yum:

There’s no direct conversion from shader to FPGA. One would have to design a circuit that does the necessary calculations and then model that circuit in HDL. That’s how the Hqx scalers were created. ScaleFx is substantially more complicated than those, though.

Sure, on the last pass, you can start it out with (pseudocode):

if (luminance > 0.5 [or whatever])
{
  FragCoord = unfiltered;
}
else
{
  [the current shader]
}

I want to know if it is possible to filter games with 2D elements and 3D objects with ScaleFX. ScaleFX has very good results combined with smartblur and soft4x. I would like to apply the results also in games with an combination of 2D and 3D elements.

XBR started a similar attempt.

pretty much what hunterk said. I don’t know much about FPGA design. but I can imagine that it might be a real challenge since ScaleFX is a rather convoluted algorithm by design. unlike HQx it goes through several passes which would require more memory than these pixel filter from previous generations. converting the filter to 1pass would result in an absurdly big kernel with a radius probably around 20 to 30. well I’m glad I don’t have to do that lol

wasn’t there something going on with retroarch development in general or specific kernels which would allow to filter the 2D plane separately? anyways, I’d use the same approach as Hyllian. since ScaleFX works at 3x the user would need to use integer scaling with the factor 3. then look at each 3x3 window and determine if all subpixels are of the same color. if that is the case then assume it is part of the 2D plane otherwise it’s 3D. this requires of course that these elements aren’t texture filtered, otherwise this approach fails. with that we get a mask we can use to apply our pixel filter to the 2D elements of the picture. the idea is definitely on my schedule :slight_smile:

1 Like

Maybe you can also support me with another issue, Sp00kyFox

I would like to apply fast-bilateral and deposterize before scaleFX. With the cg shader it is working without any issue. However with glsl the picture becomes pixelated and blocky. If I remove bilateral and deposterize, scalefx is working as intended. Slang has the same issue as glsl.

This is my preset:

I modified the GLSL version to use relative shader pass references instead of absolute ones. Slang doesn’t support the relative references, so I would need to add a blank “reference” pass before the scalefx passes and then you would replace that pass with your pre-passes. It’s more work, and having the dummy pass at the beginning would slow down the shader slightly for everyone, so I’m not sure if it’s something we should do in general.

1 Like

You are incredible. Thank you! Tested already the GLSL shaders with success.

1 Like

Awesome, I’m gonna have to try out an undithering pass before scalefx now.

Hi @Sp00kyFox,

Looking at the example images, ScaleFX performs fantastic pixel art interpretations, I’m in awe.

Is there a way to process individual images (like a PNG) with ScaleFX, maybe in a simple UI, a Windows Command Line command or a MacOS Terminal command? I’d be happy to donate for the development of that if it isn’t already possible.

Many thanks,

Metin

not yet, sry. only thing you can do right now is to use the imageviewer core in retroarch. of course this doesn’t allow you to do any batch processing.

I just opened an issue to add a CLI option to “run for X frames, take a screenshot and then close”. That should be usable for scripted/batch shader processing.

1 Like

Many thanks @Sp00kyFox and @hunterk.

I’ve only got macOS, and would love to have something like the Scaler Test tool that can be found on the xBRZ page, but featuring ScaleFX, so I could upscale pixel art with it. If a GUI is too much hassle, a macOS Terminal command would be great as well. I’d gladly donate something for that.

You’re very welcome to e-mail me and discuss this. My e-mail can be found on my site.

Thanks!

Metin

I promised :stuck_out_tongue_winking_eye: i will take a look at the hybrid version and maybe create a nice preset. Old leve2-aa does some line blending, aswell as the hybrid itself, so i created a new aa variation without it. It’s a bit nicer overall and the preset might be a good all-rounder since the hybrid improved from the original version too.

Here is a screenshot comparison to show it’s still level2-aa, but with much less line blur. http://screenshotcomparison.com/comparison/118966

aa-shader-4.0-level2-pass1-noblend.glsl

/*
   Copyright (C) 2018 guest(r) - [email protected]

   This program 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.

   This program 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 this program; if not, write to the Free Software
   Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.
*/

#pragma parameter AAOFFSET "AA offset first pass" 1.0 0.25 2.0 0.05 

#if defined(VERTEX)

#if __VERSION__ >= 130
#define COMPAT_VARYING out
#define COMPAT_ATTRIBUTE in
#define COMPAT_TEXTURE texture
#else
#define COMPAT_VARYING varying 
#define COMPAT_ATTRIBUTE attribute 
#define COMPAT_TEXTURE texture2D
#endif

#ifdef GL_ES
#define COMPAT_PRECISION mediump
#else
#define COMPAT_PRECISION
#endif

COMPAT_ATTRIBUTE vec4 VertexCoord;
COMPAT_ATTRIBUTE vec4 COLOR;
COMPAT_ATTRIBUTE vec4 TexCoord;
COMPAT_VARYING vec4 COL0;
COMPAT_VARYING vec4 TEX0;

vec4 _oPosition1; 
uniform mat4 MVPMatrix;
uniform COMPAT_PRECISION int FrameDirection;
uniform COMPAT_PRECISION int FrameCount;
uniform COMPAT_PRECISION vec2 OutputSize;
uniform COMPAT_PRECISION vec2 TextureSize;
uniform COMPAT_PRECISION vec2 InputSize;

// compatibility #defines
#define vTexCoord TEX0.xy
#define SourceSize vec4(TextureSize, 1.0 / TextureSize) //either TextureSize or InputSize
#define OutSize vec4(OutputSize, 1.0 / OutputSize)

void main()
{
    gl_Position = MVPMatrix * VertexCoord;
    TEX0.xy = TexCoord.xy * 1.00001;
}

#elif defined(FRAGMENT)

#ifdef GL_ES
#ifdef GL_FRAGMENT_PRECISION_HIGH
precision highp float;
#else
precision mediump float;
#endif
#define COMPAT_PRECISION mediump
#else
#define COMPAT_PRECISION
#endif

#if __VERSION__ >= 130
#define COMPAT_VARYING in
#define COMPAT_TEXTURE texture
out COMPAT_PRECISION vec4 FragColor;
#else
#define COMPAT_VARYING varying
#define FragColor gl_FragColor
#define COMPAT_TEXTURE texture2D
#endif

uniform COMPAT_PRECISION int FrameDirection;
uniform COMPAT_PRECISION int FrameCount;
uniform COMPAT_PRECISION vec2 OutputSize;
uniform COMPAT_PRECISION vec2 TextureSize;
uniform COMPAT_PRECISION vec2 InputSize;
uniform sampler2D Texture;
COMPAT_VARYING vec4 TEX0;

// compatibility #defines
#define Source Texture
#define vTexCoord TEX0.xy

#define SourceSize vec4(TextureSize, 1.0 / TextureSize) //either TextureSize or InputSize
#define OutSize vec4(OutputSize, 1.0 / OutputSize)

#ifdef PARAMETER_UNIFORM
uniform COMPAT_PRECISION float AAOFFSET;
#else
#define AAOFFSET  1.0
#endif 

void main()
{
   vec2 tex = vTexCoord;	
   vec2 texsize = SourceSize.xy;
   float dx = AAOFFSET/texsize.x;
   float dy = AAOFFSET/texsize.y;
   vec3 dt = vec3(1.0, 1.0, 1.0);
   
   vec4 yx = vec4( dx, dy,-dx,-dy);
   vec4 xh = vec4( 1.4*dx, 4.4*dy, -1.4*dx, -4.4*dy);
   vec4 yv = vec4( 4.4*dx, 1.4*dy, -4.4*dx, -1.4*dy);
   vec2 xx = vec2( 3.4*dx, 0.0);
   vec2 yy = vec2( 0.0, 3.4*dy);
   
   vec3 c11 = COMPAT_TEXTURE(Source, tex        ).xyz;  
   vec3 s00 = COMPAT_TEXTURE(Source, tex + yx.zw).xyz;
   vec3 s20 = COMPAT_TEXTURE(Source, tex + yx.xw).xyz;
   vec3 s22 = COMPAT_TEXTURE(Source, tex + yx.xy).xyz;
   vec3 s02 = COMPAT_TEXTURE(Source, tex + yx.zy).xyz;
   vec3 h00 = COMPAT_TEXTURE(Source, tex + xh.zw).xyz;
   vec3 h20 = COMPAT_TEXTURE(Source, tex + xh.xw).xyz;
   vec3 h22 = COMPAT_TEXTURE(Source, tex + xh.xy).xyz;
   vec3 h02 = COMPAT_TEXTURE(Source, tex + xh.zy).xyz;
   vec3 v00 = COMPAT_TEXTURE(Source, tex + yv.zw).xyz;
   vec3 v20 = COMPAT_TEXTURE(Source, tex + yv.xw).xyz;
   vec3 v22 = COMPAT_TEXTURE(Source, tex + yv.xy).xyz;
   vec3 v02 = COMPAT_TEXTURE(Source, tex + yv.zy).xyz;
   vec3 c10 = COMPAT_TEXTURE(Source, tex - yy   ).xyz;     
   vec3 c21 = COMPAT_TEXTURE(Source, tex + xx   ).xyz;
   vec3 c12 = COMPAT_TEXTURE(Source, tex + yy   ).xyz;
   vec3 c01 = COMPAT_TEXTURE(Source, tex - xx   ).xyz;
   
   float m1=1.0/(dot(abs(s00-s22),dt)+0.00001);
   float m2=1.0/(dot(abs(s02-s20),dt)+0.00001);
   float h1=1.0/(dot(abs(c10-h22),dt)+0.00001);
   float h2=1.0/(dot(abs(c12-h20),dt)+0.00001);
   float h3=1.0/(dot(abs(h00-c12),dt)+0.00001);
   float h4=1.0/(dot(abs(h02-c10),dt)+0.00001);
   float v1=1.0/(dot(abs(c01-v22),dt)+0.00001);
   float v2=1.0/(dot(abs(c01-v20),dt)+0.00001);
   float v3=1.0/(dot(abs(v00-c21),dt)+0.00001);
   float v4=1.0/(dot(abs(v02-c21),dt)+0.00001);

   vec3 t1 = 0.5*(m1*(s00+s22)+m2*(s02+s20))/(m1+m2);
   vec3 t2 = 0.5*(h1*(c10+h22)+h2*(c12+h20)+h3*(h00+c12)+h4*(h02+c10))/(h1+h2+h3+h4);
   vec3 t3 = 0.5*(v1*(c01+v22)+v2*(c01+v20)+v3*(v00+c21)+v4*(v02+c21))/(v1+v2+v3+v4); 

   float k1 = 1.0/(dot(abs(t1-c11),dt)+0.00001);
   float k2 = 1.0/(dot(abs(t2-c11),dt)+0.00001);
   float k3 = 1.0/(dot(abs(t3-c11),dt)+0.00001);

   FragColor =  vec4((k1*t1 + k2*t2 + k3*t3)/(k1+k2+k3),1.0);
} 
#endif 

And the preset:

xsoft+scalefx-hybrid+level2aa.glslp

shaders = 8

shader0 = ../scalefx/shaders/scalefx-pass0.glsl
filter_linear0 = false
scale_type0 = source
scale0 = 1.0
float_framebuffer0 = true

shader1 = ../scalefx/shaders/scalefx-pass1.glsl
filter_linear1 = false
scale_type1 = source
scale1 = 1.0
float_framebuffer1 = true

shader2 = ../scalefx/shaders/scalefx-pass2.glsl
filter_linear2 = false
scale_type2 = source
scale2 = 1.0

shader3 = ../scalefx/shaders/scalefx-pass3.glsl
filter_linear3 = false
scale_type3 = source
scale3 = 1.0

shader4 = ../scalefx/shaders/scalefx-pass4-hybrid.glsl
filter_linear4 = false
scale_type4 = source
scale4 = 3.0 

shader5 = ../stock.glsl
filter_linear5 = false
scale_type5 = source
scale5 = 2.0

shader6 = ../anti-aliasing/shaders/aa-shader-4.0-level2/aa-shader-4.0-level2-pass1-noblend.glsl
filter_linear6 = true
scale_type6 = source
scale6 = 1.0

shader7 = ../anti-aliasing/shaders/aa-shader-4.0-level2/aa-shader-4.0-level2-pass2.glsl
filter_linear7 = true
scale_type7 = source
scale7 = 1.0

parameters = "SFX_CLR;SFX_SAA"
SFX_SAA = 0.00
SFX_CLR = 0.60

Cheers…:smile:

Edit: oops, now it’s hybrid version, posted the wrong preset before.

oh man, that’s really great! The AA pass keeps all of the detail of scalefx-hybrid but smooths all of the stippled edges. Beautiful!

1 Like

Meanwhile i tried (for some time, if i’m honest) to do a good deblur setup. Until recently i lacked a proper aa shader and deblur had it’s issues, but i managed to resolve them now.

I’m posting the shader/preset if anyone cares to try it out. :sweat_smile:

Here is a comparison with the above preset agains deblured:

http://screenshotcomparison.com/comparison/119330

deblur.glsl (goes in deblur/shaders/)

/*
   Deblur Shader
   
   Copyright (C) 2005 - 2018 guest(r) - [email protected]

   This program 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.

   This program 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 this program; if not, write to the Free Software
   Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.
*/

#pragma parameter OFFSET "Deblur offset" 2.0 1.0 4.0 0.25 
#pragma parameter DEBLUR "Deblur str.  " 2.5 1.0 7.0 0.25 
#pragma parameter SMART  "Smart deblur " 0.0 0.0 1.0 1.00 

#if defined(VERTEX)

#if __VERSION__ >= 130
#define COMPAT_VARYING out
#define COMPAT_ATTRIBUTE in
#define COMPAT_TEXTURE texture
#else
#define COMPAT_VARYING varying 
#define COMPAT_ATTRIBUTE attribute 
#define COMPAT_TEXTURE texture2D
#endif

#ifdef GL_ES
#define COMPAT_PRECISION mediump
#else
#define COMPAT_PRECISION
#endif

COMPAT_ATTRIBUTE vec4 VertexCoord;
COMPAT_ATTRIBUTE vec4 COLOR;
COMPAT_ATTRIBUTE vec4 TexCoord;
COMPAT_VARYING vec4 COL0;
COMPAT_VARYING vec4 TEX0;

vec4 _oPosition1; 
uniform mat4 MVPMatrix;
uniform COMPAT_PRECISION int FrameDirection;
uniform COMPAT_PRECISION int FrameCount;
uniform COMPAT_PRECISION vec2 OutputSize;
uniform COMPAT_PRECISION vec2 TextureSize;
uniform COMPAT_PRECISION vec2 InputSize;

// compatibility #defines
#define vTexCoord TEX0.xy
#define SourceSize vec4(TextureSize, 1.0 / TextureSize) //either TextureSize or InputSize
#define OutSize vec4(OutputSize, 1.0 / OutputSize)

void main()
{
    gl_Position = MVPMatrix * VertexCoord;
    TEX0.xy = TexCoord.xy * 1.00001;
}

#elif defined(FRAGMENT)

#ifdef GL_ES
#ifdef GL_FRAGMENT_PRECISION_HIGH
precision highp float;
#else
precision mediump float;
#endif
#define COMPAT_PRECISION mediump
#else
#define COMPAT_PRECISION
#endif

#if __VERSION__ >= 130
#define COMPAT_VARYING in
#define COMPAT_TEXTURE texture
out COMPAT_PRECISION vec4 FragColor;
#else
#define COMPAT_VARYING varying
#define FragColor gl_FragColor
#define COMPAT_TEXTURE texture2D
#endif

uniform COMPAT_PRECISION int FrameDirection;
uniform COMPAT_PRECISION int FrameCount;
uniform COMPAT_PRECISION vec2 OutputSize;
uniform COMPAT_PRECISION vec2 TextureSize;
uniform COMPAT_PRECISION vec2 InputSize;
uniform sampler2D Texture;
COMPAT_VARYING vec4 TEX0;

// compatibility #defines
#define Source Texture
#define vTexCoord TEX0.xy

#define SourceSize vec4(TextureSize, 1.0 / TextureSize) //either TextureSize or InputSize
#define OutSize vec4(OutputSize, 1.0 / OutputSize)

#ifdef PARAMETER_UNIFORM
uniform COMPAT_PRECISION float OFFSET;
uniform COMPAT_PRECISION float DEBLUR;
uniform COMPAT_PRECISION float SMART;
#else
#define OFFSET  2.0
#define DEBLUR  2.5
#define SMART   0.0
#endif 

vec3  dt = vec3(1.,1.,1.);
vec3  dtt = vec3(0.0001, 0.0001, 0.0001); 

void main()
{
   vec2 tex = vTexCoord;	
   vec2 texsize = SourceSize.xy;
   float dx = OFFSET/texsize.x;
   float dy = OFFSET/texsize.y;
   
   vec4 yx = vec4( dx, dy,-dx,-dy);
   vec2 xx = vec2( dx, 0.0);
   vec2 yy = vec2( 0.0, dy);
   
   vec3 c11 = COMPAT_TEXTURE(Source, tex        ).xyz;  
   vec3 c00 = COMPAT_TEXTURE(Source, tex + yx.zw).xyz;
   vec3 c20 = COMPAT_TEXTURE(Source, tex + yx.xw).xyz;
   vec3 c22 = COMPAT_TEXTURE(Source, tex + yx.xy).xyz;
   vec3 c02 = COMPAT_TEXTURE(Source, tex + yx.zy).xyz;
   vec3 c10 = COMPAT_TEXTURE(Source, tex - yy   ).xyz;     
   vec3 c21 = COMPAT_TEXTURE(Source, tex + xx   ).xyz;
   vec3 c12 = COMPAT_TEXTURE(Source, tex + yy   ).xyz;
   vec3 c01 = COMPAT_TEXTURE(Source, tex - xx   ).xyz;
   
   vec3 mn1 = min (min (c00,c01),c02);
   vec3 mn2 = min (min (c10,c11),c12);
   vec3 mn3 = min (min (c20,c21),c22);
   vec3 mx1 = max (max (c00,c01),c02);
   vec3 mx2 = max (max (c10,c11),c12);
   vec3 mx3 = max (max (c20,c21),c22);

   vec3 d11;
   
   mn1 = min(min(mn1,mn2),mn3);
   mx1 = max(max(mx1,mx2),mx3);

   vec3 dif1 = abs(c11-mn1) + dtt;
   vec3 dif2 = abs(c11-mx1) + dtt;
   
   float DB1 = DEBLUR; float dif;
   
   if (SMART == 1.0)
   {
       dif = max(length(dif1),length(dif2));
	   dif = min(dif, 1.0);
       DB1 = max(mix(-0.5, DEBLUR, dif), 1.0);
   }
   
   dif1=vec3(pow(dif1.x,DB1),pow(dif1.y,DB1),pow(dif1.z,DB1));
   dif2=vec3(pow(dif2.x,DB1),pow(dif2.y,DB1),pow(dif2.z,DB1));

   d11 = vec3((dif1.x*mx1.x + dif2.x*mn1.x)/(dif1.x + dif2.x),
                (dif1.y*mx1.y + dif2.y*mn1.y)/(dif1.y + dif2.y),
                (dif1.z*mx1.z + dif2.z*mn1.z)/(dif1.z + dif2.z));   
 
   float k10 = 1.0/(dot(abs(c10-d11),dt)+0.0001);  
   float k01 = 1.0/(dot(abs(c01-d11),dt)+0.0001);
   float k11 = 1.0/(dot(abs(c11-d11),dt)+0.0001);  
   float k21 = 1.0/(dot(abs(c21-d11),dt)+0.0001);
   float k12 = 1.0/(dot(abs(c12-d11),dt)+0.0001);   
   
   c11 = (k10*c10 + k01*c01 + k11*c11 + k21*c21 + k12*c12)/(k10+k01+k11+k21+k12);
   
   FragColor = vec4(c11,1.0); 
} 
#endif

Has some options and can even do smart deblur, like the 4xsoftSdB.

The preset is like above (the hybrid really doesen’t produce bad shapes): :yum:

xsoft+scalefx-hybrid+level2aa-sharp.glslp

shaders = 9

shader0 = ../scalefx/shaders/scalefx-pass0.glsl
filter_linear0 = false
scale_type0 = source
scale0 = 1.0
float_framebuffer0 = true

shader1 = ../scalefx/shaders/scalefx-pass1.glsl
filter_linear1 = false
scale_type1 = source
scale1 = 1.0
float_framebuffer1 = true

shader2 = ../scalefx/shaders/scalefx-pass2.glsl
filter_linear2 = false
scale_type2 = source
scale2 = 1.0

shader3 = ../scalefx/shaders/scalefx-pass3.glsl
filter_linear3 = false
scale_type3 = source
scale3 = 1.0

shader4 = ../scalefx/shaders/scalefx-pass4-hybrid.glsl
filter_linear4 = false
scale_type4 = source
scale4 = 3.0 

shader5 = ../stock.glsl
filter_linear5 = false
scale_type5 = source
scale5 = 2.0

shader6 = ../anti-aliasing/shaders/aa-shader-4.0-level2/aa-shader-4.0-level2-pass1-noblend.glsl
filter_linear6 = true
scale_type6 = source
scale6 = 1.0

shader7 = ../anti-aliasing/shaders/aa-shader-4.0-level2/aa-shader-4.0-level2-pass2.glsl
filter_linear7 = true
scale_type7 = source
scale7 = 1.0

shader8 = ../deblur/shaders/deblur.glsl
filter_linear8 = true
scale_type8 = source
scale8 = 1.0

parameters = "SFX_CLR;SFX_SAA"
SFX_SAA = 0.00
SFX_CLR = 0.60

Now i’m off to grind some rep. :grin:

Edit: Much better smart version (option) of deblur.

1 Like

nice work! It reminds me of when I take my glasses off/on :smiley:

1 Like

This reminds me of self-similarity / fractal image upscaling. You may find some of the papers on it inspiring.

good stuff guest.r! I’m actually working on this currently. thing is that pixelart interpolation and reverseAA don’t really mix since both require the unscaled source image. the hybrid version of ScaleFX is really just a dirty hack and not a proper merge of both algorithms. but I’m confident to say that I’ve come up with a solution that will replace the hybrid approach. this is a post process shader that you can simply add on top of ScaleFX (or other filters for that matter).

ScaleFX

ScaleFX-Hybrid

ScaleFX + rAA_post

this is with no additional shaders. just ScaleFX and a generalized rAA version I’m working on. I’ll post more soon.

2 Likes

so after thinking it through I came to the conclusion that reverseAA can’t really be properly combined with edge interpolation filters. while there is a hybrid version of ScaleFX it is a little rough. so instead of continuing this hybrid approach I decided to make a post-process version of rAA which can be applied to an already upscaled image. therefore it can be used with other filters as well not only ScaleFX but xBR or others too.

this rAA version works on a 1x scale factor and assumes the image is already a 3x upscale. just add the two passes on top of your shader stack (that results in a 3x image) and you’re good to go. the filter is less performant than the original rAA since it requires more pixel fetches. beside the known sharpness parameter there is a new “gradient protection” one that well protects pixels that don’t need changing.

right now I have only a cg version. if someone wants to help out translating it to the other shader languages, be my guest. I included a new shader preset in the scalefx folder which demonstrates it and makes the hybrid version obsolete. just extract the archive to your retroarch folder and you’re done.

download

ScaleFX

ScaleFX + rAA

1 Like