diff --git a/runelite-client/src/main/java/net/runelite/client/plugins/gpu/GpuPlugin.java b/runelite-client/src/main/java/net/runelite/client/plugins/gpu/GpuPlugin.java index d17989c222..a34ff0510e 100644 --- a/runelite-client/src/main/java/net/runelite/client/plugins/gpu/GpuPlugin.java +++ b/runelite-client/src/main/java/net/runelite/client/plugins/gpu/GpuPlugin.java @@ -146,10 +146,6 @@ public class GpuPlugin extends Plugin implements DrawCallbacks private int glUiVertexShader; private int glUiFragmentShader; - private int glUiBicubicProgram; - private int glUiBicubicVertexShader; - private int glUiBicubicFragmentShader; - private int vaoUiHandle; private int vboUiHandle; @@ -223,7 +219,7 @@ public class GpuPlugin extends Plugin implements DrawCallbacks private int uniProjectionMatrix; private int uniBrightness; private int uniTex; - private int uniTexBicubic; + private int uniTexSamplingMode; private int uniTextures; private int uniTextureOffsets; private int uniBlockSmall; @@ -510,15 +506,6 @@ public class GpuPlugin extends Plugin implements DrawCallbacks null, inputStreamToString(getClass().getResourceAsStream("fragui.glsl"))); - glUiBicubicProgram = gl.glCreateProgram(); - glUiBicubicVertexShader = gl.glCreateShader(gl.GL_VERTEX_SHADER); - glUiBicubicFragmentShader = gl.glCreateShader(gl.GL_FRAGMENT_SHADER); - GLUtil.loadShaders(gl, glUiBicubicProgram, glUiBicubicVertexShader, -1, glUiBicubicFragmentShader, - inputStreamToString(getClass().getResourceAsStream("vertui.glsl")), - null, - inputStreamToString(getClass().getResourceAsStream("fragui_bicubic.glsl"))); - - initUniforms(); } @@ -533,7 +520,7 @@ public class GpuPlugin extends Plugin implements DrawCallbacks uniDrawDistance = gl.glGetUniformLocation(glProgram, "drawDistance"); uniTex = gl.glGetUniformLocation(glUiProgram, "tex"); - uniTexBicubic = gl.glGetUniformLocation(glUiBicubicProgram, "tex"); + uniTexSamplingMode = gl.glGetUniformLocation(glUiProgram, "samplingMode"); uniTextures = gl.glGetUniformLocation(glProgram, "textures"); uniTextureOffsets = gl.glGetUniformLocation(glProgram, "textureOffsets"); @@ -586,9 +573,6 @@ public class GpuPlugin extends Plugin implements DrawCallbacks gl.glDeleteProgram(glUiProgram); glUiProgram = -1; - - gl.glDeleteProgram(glUiBicubicProgram); - glUiBicubicProgram = -1; } private void initVao() @@ -1182,18 +1166,10 @@ public class GpuPlugin extends Plugin implements DrawCallbacks glDpiAwareViewport(0, 0, canvasWidth, canvasHeight); } - if (client.isStretchedEnabled() && config.uiScalingMode() == UIScalingMode.CATMULL_ROM) - { - // Use the texture bound in the first pass - gl.glUseProgram(glUiBicubicProgram); - gl.glUniform1i(uniTexBicubic, 0); - } - else - { - // Use the texture bound in the first pass - gl.glUseProgram(glUiProgram); - gl.glUniform1i(uniTex, 0); - } + // Use the texture bound in the first pass + gl.glUseProgram(glUiProgram); + gl.glUniform1i(uniTex, 0); + gl.glUniform1i(uniTexSamplingMode, config.uiScalingMode().getMode()); // Set the sampling function used when stretching the UI. // This is probably better done with sampler objects instead of texture parameters, but this is easier and likely more portable. diff --git a/runelite-client/src/main/java/net/runelite/client/plugins/gpu/config/UIScalingMode.java b/runelite-client/src/main/java/net/runelite/client/plugins/gpu/config/UIScalingMode.java index 63c3755e6d..846d96f335 100644 --- a/runelite-client/src/main/java/net/runelite/client/plugins/gpu/config/UIScalingMode.java +++ b/runelite-client/src/main/java/net/runelite/client/plugins/gpu/config/UIScalingMode.java @@ -31,11 +31,12 @@ import lombok.RequiredArgsConstructor; @RequiredArgsConstructor public enum UIScalingMode { - NEAREST("Nearest Neighbor"), - LINEAR("Bilinear"), - CATMULL_ROM("Bicubic (Catmull-Rom)"); + NEAREST("Nearest Neighbor", 0), + LINEAR("Bilinear", 0), + CATMULL_ROM("Bicubic (Catmull-Rom)", 1); private final String name; + private final int mode; @Override public String toString() diff --git a/runelite-client/src/main/resources/net/runelite/client/plugins/gpu/fragui.glsl b/runelite-client/src/main/resources/net/runelite/client/plugins/gpu/fragui.glsl index 57731df5b8..fec68566a5 100644 --- a/runelite-client/src/main/resources/net/runelite/client/plugins/gpu/fragui.glsl +++ b/runelite-client/src/main/resources/net/runelite/client/plugins/gpu/fragui.glsl @@ -26,11 +26,93 @@ uniform sampler2D tex; +// Modes: +// 0 - default sampling, either GL_LINEAR or GL_NEAREST depending on texture attributes +// 1 - bicubic sampling with Catmull-Rom spline values +uniform int samplingMode; + in vec2 TexCoord; out vec4 FragColor; -void main() { - vec4 c = texture(tex, TexCoord); - FragColor = c; +// Cubic filter with Catmull-Rom parameters +float catmull_rom(float x) +{ + /* A generalized cubic filter as described by Mitchell and Netravali is defined by the piecewise equation: + * if abs(x) < 1 + * y = 1/6 * ( (12 - 9b - 6c) * abs(x)^3 + (-18 + 12b + 6c) * abs(x)^2 + (6 - 2b) ) + * if abs(x) >= 1 and < 2 + * y = 1/6 * ( (-1b - 6c) * abs(x)^3 + (6b + 30c) * abs(x)^2 + (-12b - 48c) * abs(x) + (8b + 24c) ) + * otherwise + * y = 0 + * Generally favorable results in image upscaling are given by the values b = 0 and c = 0.5. + * This is known as the Catmull-Rom filter. + * Placing these values into the piecewise equations gives us a more compact representation of: + * y = 1.5 * abs(x)^3 - 2.5 * abs(x)^2 + 1 // abs(x) < 1 + * y = -0.5 * abs(x)^3 + 2.5 * abs(x)^2 - 4 * abs(x) + 2 // 1 <= abs(x) < 2 + */ + + float t = abs(x); // absolute value of the x coordinate + float t2 = t * t; // t squared + float t3 = t * t * t; // t cubed + + if (t < 1) + return 1.5 * t3 - 2.5 * t2 + 1; + else if (t < 2) + return -0.5 * t3 + 2.5 * t2 - 4 * t + 2; + else + return 0; +} + +// Samples a texture using a 4x4 filtering kernel. +vec4 textureFiltered(sampler2D sampler, vec2 texCoords){ + vec2 texSize = textureSize(tex, 0); + vec2 texelSize = 1.0 / texSize; + texCoords *= texSize; + texCoords -= 0.5; + + vec4 nSum = vec4( 0.0, 0.0, 0.0, 0.0 ); + vec4 nDenom = vec4( 0.0, 0.0, 0.0, 0.0 ); + + ivec2 texelCoords = ivec2(floor(texCoords)); + vec2 coordFract = fract(texCoords); + + if (samplingMode == 1) + { + for (int m = -1; m <= 2; m++) + { + for (int n = -1; n <= 2; n++) + { + // get the raw texel, bypassing any other filters + vec4 vecData = texelFetch(sampler, texelCoords + ivec2(m, n), 0); + + // calculate weights based on distance of the current texel offset from the sub-texel position of the sampling location + float cx = catmull_rom( m - coordFract.x ); + float cy = catmull_rom( n - coordFract.y ); + + // build the weighted average + nSum += vecData * cx * cy; + nDenom += cx * cy; + } + } + } + else + { + // Undefined sampling mode, fall back to default sampler + return texture(sampler, texCoords); + } + + // calculate and return the weighted average + return nSum / nDenom; +} + +void main() { + vec4 c; + + if (samplingMode == 0) + c = texture(tex, TexCoord); + else + c = textureFiltered(tex, TexCoord); + + FragColor = c; } diff --git a/runelite-client/src/main/resources/net/runelite/client/plugins/gpu/fragui_bicubic.glsl b/runelite-client/src/main/resources/net/runelite/client/plugins/gpu/fragui_bicubic.glsl deleted file mode 100644 index 1ef39b3beb..0000000000 --- a/runelite-client/src/main/resources/net/runelite/client/plugins/gpu/fragui_bicubic.glsl +++ /dev/null @@ -1,98 +0,0 @@ -/* - * Copyright (c) 2019 logarrhytmic - * All rights reserved. - * - * Redistribution and use in source and binary forms, with or without - * modification, are permitted provided that the following conditions are met: - * - * 1. Redistributions of source code must retain the above copyright notice, this - * list of conditions and the following disclaimer. - * 2. Redistributions in binary form must reproduce the above copyright notice, - * this list of conditions and the following disclaimer in the documentation - * and/or other materials provided with the distribution. - * - * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND - * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED - * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE - * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR - * ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES - * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; - * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND - * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT - * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS - * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - */ -#version 330 - -uniform sampler2D tex; - -in vec2 TexCoord; - -out vec4 FragColor; - -float CatmullRom( float x ) -{ - const float B = 0.0; - const float C = 0.5; - - float f = abs(x); - float f2 = f * f; - float f3 = f * f * f; - - if (f >= 2) { - return 0.0; - } - - if ( f < 1.0 ) { - return ( - ( 12 - 9 * B - 6 * C ) * f3 + - ( -18 + 12 * B + 6 *C ) * f2 + - ( 6 - 2 * B ) - ) / 6.0; - } - else { - return ( - ( -B - 6 * C ) * f3 + - ( 6 * B + 30 * C ) * f2 + - ( - ( 12 * B ) - 48 * C ) * f + - 8 * B + 24 * C - ) / 6.0; - } -} - -vec4 textureBicubic(sampler2D sampler, vec2 texCoords){ - vec2 texSize = textureSize(tex, 0); - vec2 texelSize = 1.0 / texSize; - texCoords *= texSize; - texCoords -= 0.5; - - vec4 nSum = vec4( 0.0, 0.0, 0.0, 0.0 ); - vec4 nDenom = vec4( 0.0, 0.0, 0.0, 0.0 ); - - ivec2 texelCoords = ivec2(floor(texCoords)); - vec2 fxy = fract(texCoords); - - for (int m = -1; m <= 2; m++) - { - for (int n = -1; n <= 2; n++) - { - vec4 vecData = texelFetch( - sampler, - texelCoords + ivec2(m, n), - 0 - ); - - float cx = CatmullRom( m - fxy.x ); - float cy = CatmullRom( n - fxy.y ); - - nSum += vecData * cx * cy; - nDenom += cx * cy; - } - } - return nSum / nDenom; -} - -void main() { - vec4 c = textureBicubic(tex, TexCoord); - FragColor = c; -} \ No newline at end of file