/* FSR - [EASU] EDGE ADAPTIVE SPATIAL UPSAMPLING Ported from https://www.shadertoy.com/view/stXSWB, MIT license */ #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; 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; void main() { gl_Position = MVPMatrix * VertexCoord; COL0 = COLOR; TEX0.xy = TexCoord.xy; } #elif defined(FRAGMENT) #if __VERSION__ >= 130 #define COMPAT_VARYING in #define COMPAT_TEXTURE texture out vec4 FragColor; #else #define COMPAT_VARYING varying #define FragColor gl_FragColor #define COMPAT_TEXTURE texture2D #endif #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 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) vec3 FsrEasuCF(vec2 p) { return COMPAT_TEXTURE(Source,p).rgb; } /**** EASU ****/ void FsrEasuCon( out vec4 con0, out vec4 con1, out vec4 con2, out vec4 con3, // This the rendered image resolution being upscaled vec2 inputViewportInPixels, // This is the resolution of the resource containing the input image (useful for dynamic resolution) vec2 inputSizeInPixels, // This is the display resolution which the input image gets upscaled to vec2 outputSizeInPixels ) { // Output integer position to a pixel position in viewport. con0 = vec4( inputViewportInPixels.x/outputSizeInPixels.x, inputViewportInPixels.y/outputSizeInPixels.y, .5*inputViewportInPixels.x/outputSizeInPixels.x-.5, .5*inputViewportInPixels.y/outputSizeInPixels.y-.5 ); // Viewport pixel position to normalized image space. // This is used to get upper-left of 'F' tap. con1 = vec4(1,1,1,-1)/inputSizeInPixels.xyxy; // Centers of gather4, first offset from upper-left of 'F'. // +---+---+ // | | | // +--(0)--+ // | b | c | // +---F---+---+---+ // | e | f | g | h | // +--(1)--+--(2)--+ // | i | j | k | l | // +---+---+---+---+ // | n | o | // +--(3)--+ // | | | // +---+---+ // These are from (0) instead of 'F'. con2 = vec4(-1,2,1,2)/inputSizeInPixels.xyxy; con3 = vec4(0,4,0,0)/inputSizeInPixels.xyxy; } // Filtering for a given tap for the scalar. void FsrEasuTapF( inout vec3 aC, // Accumulated color, with negative lobe. inout float aW, // Accumulated weight. vec2 off, // Pixel offset from resolve position to tap. vec2 dir, // Gradient direction. vec2 len, // Length. float lob, // Negative lobe strength. float clp, // Clipping point. vec3 c ) { // Tap color. // Rotate offset by direction. vec2 v = vec2(dot(off, dir), dot(off,vec2(-dir.y,dir.x))); // Anisotropy. v *= len; // Compute distance^2. float d2 = min(dot(v,v),clp); // Limit to the window as at corner, 2 taps can easily be outside. // Approximation of lancos2 without sin() or rcp(), or sqrt() to get x. // (25/16 * (2/5 * x^2 - 1)^2 - (25/16 - 1)) * (1/4 * x^2 - 1)^2 // |_______________________________________| |_______________| // base window // The general form of the 'base' is, // (a*(b*x^2-1)^2-(a-1)) // Where 'a=1/(2*b-b^2)' and 'b' moves around the negative lobe. float wB = .4 * d2 - 1.; float wA = lob * d2 -1.; wB *= wB; wA *= wA; wB = 1.5625*wB-.5625; float w= wB * wA; // Do weighted average. aC += c*w; aW += w; } //------------------------------------------------------------------------------------------------------------------------------ // Accumulate direction and length. void FsrEasuSetF( inout vec2 dir, inout float len, float w, float lA,float lB,float lC,float lD,float lE ) { // Direction is the '+' diff. // a // b c d // e // Then takes magnitude from abs average of both sides of 'c'. // Length converts gradient reversal to 0, smoothly to non-reversal at 1, shaped, then adding horz and vert terms. float lenX = max(abs(lD - lC), abs(lC - lB)); float dirX = lD - lB; dir.x += dirX * w; lenX = clamp(abs(dirX)/lenX,0.,1.); lenX *= lenX; len += lenX * w; // Repeat for the y axis. float lenY = max(abs(lE - lC), abs(lC - lA)); float dirY = lE - lA; dir.y += dirY * w; lenY = clamp(abs(dirY) / lenY,0.,1.); lenY *= lenY; len += lenY * w; } //------------------------------------------------------------------------------------------------------------------------------ void FsrEasuF( out vec3 pix, vec2 ip, // Integer pixel position in output. // Constants generated by FsrEasuCon(). vec4 con0, // xy = output to input scale, zw = first pixel offset correction vec4 con1, vec4 con2, vec4 con3 ) { //------------------------------------------------------------------------------------------------------------------------------ // Get position of 'f'. vec2 pp = ip * con0.xy + con0.zw; // Corresponding input pixel/subpixel vec2 fp = floor(pp);// fp = source nearest pixel pp -= fp; // pp = source subpixel //------------------------------------------------------------------------------------------------------------------------------ // 12-tap kernel. // b c // e f g h // i j k l // n o // Gather 4 ordering. // a b // r g vec2 p0 = fp * con1.xy + con1.zw; // These are from p0 to avoid pulling two constants on pre-Navi hardware. vec2 p1 = p0 + con2.xy; vec2 p2 = p0 + con2.zw; vec2 p3 = p0 + con3.xy; // TextureGather is not available on WebGL2 vec4 off = vec4(-.5,.5,-.5,.5)*con1.xxyy; // textureGather to texture offsets // x=west y=east z=north w=south vec3 bC = FsrEasuCF(p0 + off.xw); float bL = bC.g + 0.5 *(bC.r + bC.b); vec3 cC = FsrEasuCF(p0 + off.yw); float cL = cC.g + 0.5 *(cC.r + cC.b); vec3 iC = FsrEasuCF(p1 + off.xw); float iL = iC.g + 0.5 *(iC.r + iC.b); vec3 jC = FsrEasuCF(p1 + off.yw); float jL = jC.g + 0.5 *(jC.r + jC.b); vec3 fC = FsrEasuCF(p1 + off.yz); float fL = fC.g + 0.5 *(fC.r + fC.b); vec3 eC = FsrEasuCF(p1 + off.xz); float eL = eC.g + 0.5 *(eC.r + eC.b); vec3 kC = FsrEasuCF(p2 + off.xw); float kL = kC.g + 0.5 *(kC.r + kC.b); vec3 lC = FsrEasuCF(p2 + off.yw); float lL = lC.g + 0.5 *(lC.r + lC.b); vec3 hC = FsrEasuCF(p2 + off.yz); float hL = hC.g + 0.5 *(hC.r + hC.b); vec3 gC = FsrEasuCF(p2 + off.xz); float gL = gC.g + 0.5 *(gC.r + gC.b); vec3 oC = FsrEasuCF(p3 + off.yz); float oL = oC.g + 0.5 *(oC.r + oC.b); vec3 nC = FsrEasuCF(p3 + off.xz); float nL = nC.g + 0.5 *(nC.r + nC.b); //------------------------------------------------------------------------------------------------------------------------------ // Simplest multi-channel approximate luma possible (luma times 2, in 2 FMA/MAD). // Accumulate for bilinear interpolation. vec2 dir = vec2(0); float len = 0.; FsrEasuSetF(dir, len, (1.-pp.x)*(1.-pp.y), bL, eL, fL, gL, jL); FsrEasuSetF(dir, len, pp.x *(1.-pp.y), cL, fL, gL, hL, kL); FsrEasuSetF(dir, len, (1.-pp.x)* pp.y , fL, iL, jL, kL, nL); FsrEasuSetF(dir, len, pp.x * pp.y , gL, jL, kL, lL, oL); //------------------------------------------------------------------------------------------------------------------------------ // Normalize with approximation, and cleanup close to zero. vec2 dir2 = dir * dir; float dirR = dir2.x + dir2.y; bool zro = dirR < (1.0/32768.0); dirR = inversesqrt(dirR); dirR = zro ? 1.0 : dirR; dir.x = zro ? 1.0 : dir.x; dir *= vec2(dirR); // Transform from {0 to 2} to {0 to 1} range, and shape with square. len = len * 0.5; len *= len; // Stretch kernel {1.0 vert|horz, to sqrt(2.0) on diagonal}. float stretch = dot(dir,dir) / (max(abs(dir.x), abs(dir.y))); // Anisotropic length after rotation, // x := 1.0 lerp to 'stretch' on edges // y := 1.0 lerp to 2x on edges vec2 len2 = vec2(1. +(stretch-1.0)*len, 1. -.5 * len); // Based on the amount of 'edge', // the window shifts from +/-{sqrt(2.0) to slightly beyond 2.0}. float lob = .5 - .29 * len; // Set distance^2 clipping point to the end of the adjustable window. float clp = 1./lob; //------------------------------------------------------------------------------------------------------------------------------ // Accumulation mixed with min/max of 4 nearest. // b c // e f g h // i j k l // n o vec3 min4 = min(min(fC,gC),min(jC,kC)); vec3 max4 = max(max(fC,gC),max(jC,kC)); // Accumulation. vec3 aC = vec3(0); float aW = 0.; FsrEasuTapF(aC, aW, vec2( 0,-1)-pp, dir, len2, lob, clp, bC); FsrEasuTapF(aC, aW, vec2( 1,-1)-pp, dir, len2, lob, clp, cC); FsrEasuTapF(aC, aW, vec2(-1, 1)-pp, dir, len2, lob, clp, iC); FsrEasuTapF(aC, aW, vec2( 0, 1)-pp, dir, len2, lob, clp, jC); FsrEasuTapF(aC, aW, vec2( 0, 0)-pp, dir, len2, lob, clp, fC); FsrEasuTapF(aC, aW, vec2(-1, 0)-pp, dir, len2, lob, clp, eC); FsrEasuTapF(aC, aW, vec2( 1, 1)-pp, dir, len2, lob, clp, kC); FsrEasuTapF(aC, aW, vec2( 2, 1)-pp, dir, len2, lob, clp, lC); FsrEasuTapF(aC, aW, vec2( 2, 0)-pp, dir, len2, lob, clp, hC); FsrEasuTapF(aC, aW, vec2( 1, 0)-pp, dir, len2, lob, clp, gC); FsrEasuTapF(aC, aW, vec2( 1, 2)-pp, dir, len2, lob, clp, oC); FsrEasuTapF(aC, aW, vec2( 0, 2)-pp, dir, len2, lob, clp, nC); //------------------------------------------------------------------------------------------------------------------------------ // Normalize and dering. pix=min(max4,max(min4,aC/aW)); } void main() { vec3 c; vec4 con0,con1,con2,con3; vec2 fragCoord = vTexCoord.xy * OutputSize.xy; FsrEasuCon( con0, con1, con2, con3, SourceSize.xy, SourceSize.xy, OutputSize.xy ); FsrEasuF(c, fragCoord, con0, con1, con2, con3); FragColor = vec4(c.xyz, 1); } #endif