From fbce707cac32ca2b3f9fbd6d3ed7394a8404ebb3 Mon Sep 17 00:00:00 2001 From: minjaesong Date: Fri, 21 Jul 2023 13:14:02 +0900 Subject: [PATCH] option for screen filtering mode --- assets/locales/en/terrarum.json | 4 +- assets/locales/koKR/terrarum.json | 3 +- src/net/torvald/terrarum/App.java | 70 ++++++++++++--- src/net/torvald/terrarum/DefaultConfig.kt | 1 + .../torvald/terrarum/TerrarumPostProcessor.kt | 52 +++++------ .../modulebasegame/ui/ControlPanelCommon.kt | 35 ++++++-- .../ui/UIGraphicsControlPanel.kt | 1 + src/shaders/hq2x.frag | 87 +++++++++++++++++++ src/shaders/hq2x.tga | 3 + src/shaders/hq2x.vert | 41 +++++++++ 10 files changed, 250 insertions(+), 47 deletions(-) create mode 100644 src/shaders/hq2x.frag create mode 100644 src/shaders/hq2x.tga create mode 100644 src/shaders/hq2x.vert diff --git a/assets/locales/en/terrarum.json b/assets/locales/en/terrarum.json index 224f4b31b..5d9b16a83 100644 --- a/assets/locales/en/terrarum.json +++ b/assets/locales/en/terrarum.json @@ -32,7 +32,9 @@ "MENU_OPTIONS_PARTICLES": "Particles", "MENU_OPTIONS_PERFORMANCE": "Performance", "MENU_OPTIONS_STREAMERS_LAYOUT": "Chat Overlay", + "MENU_OPTIONS_NONE" : "None", "MENU_CREDIT_GPL_DNT" : "GPL", - "MENU_LABEL_JVM_DNT" : "JVM" + "MENU_LABEL_JVM_DNT" : "JVM", + "MENU_OPTIONS_FILTERING_HQ2X_DNT" : "Hq2x" } \ No newline at end of file diff --git a/assets/locales/koKR/terrarum.json b/assets/locales/koKR/terrarum.json index fcdc7d15a..bd294af1b 100644 --- a/assets/locales/koKR/terrarum.json +++ b/assets/locales/koKR/terrarum.json @@ -31,5 +31,6 @@ "MENU_OPTIONS_NOTIFICATION_DISPLAY_DURATION": "알림 표시 시간", "MENU_OPTIONS_PARTICLES": "입자 수", "MENU_OPTIONS_PERFORMANCE": "성능", - "MENU_OPTIONS_STREAMERS_LAYOUT": "채팅창 오버레이" + "MENU_OPTIONS_STREAMERS_LAYOUT": "채팅창 오버레이", + "MENU_OPTIONS_NONE" : "없음" } diff --git a/src/net/torvald/terrarum/App.java b/src/net/torvald/terrarum/App.java index c3bf4ce65..6fc4463f8 100644 --- a/src/net/torvald/terrarum/App.java +++ b/src/net/torvald/terrarum/App.java @@ -31,7 +31,6 @@ import net.torvald.terrarum.langpack.Lang; import net.torvald.terrarum.modulebasegame.IngameRenderer; import net.torvald.terrarum.modulebasegame.TerrarumIngame; import net.torvald.terrarum.modulebasegame.ui.ItemSlotImageFactory; -import net.torvald.terrarum.savegame.DiskSkimmer; import net.torvald.terrarum.serialise.WriteConfig; import net.torvald.terrarum.ui.Toolkit; import net.torvald.terrarum.utils.JsonFetcher; @@ -240,8 +239,12 @@ public class App implements ApplicationListener { public static ShaderProgram shaderColLUT; public static ShaderProgram shaderReflect; public static ShaderProgram shaderGhastlyWhite; + public static ShaderProgram shaderHQ2x; + + private static Texture hq2xLut; public static Mesh fullscreenQuad; + public static Mesh fullscreenQuad2x; private static OrthographicCamera camera; private static FlippingSpriteBatch logoBatch; public static TextureRegion logo; @@ -392,6 +395,7 @@ public class App implements ApplicationListener { appConfig.useVsync(getConfigBoolean("usevsync")); appConfig.setResizable(false); appConfig.setWindowedMode(width, height); + appConfig.setTransparentFramebuffer(false); int fpsActive = Math.min(GLOBAL_FRAMERATE_LIMIT, getConfigInt("displayfps")); if (fpsActive <= 0) fpsActive = GLOBAL_FRAMERATE_LIMIT; int fpsBack = Math.min(GLOBAL_FRAMERATE_LIMIT, getConfigInt("displayfpsidle")); @@ -473,6 +477,9 @@ public class App implements ApplicationListener { ); shaderPassthruRGBA = loadShaderFromClasspath("shaders/gl32spritebatch.vert", "shaders/gl32spritebatch.frag"); shaderReflect = loadShaderFromClasspath("shaders/default.vert", "shaders/reflect.frag"); + shaderHQ2x = loadShaderFromClasspath("shaders/hq2x.vert", "shaders/hq2x.frag"); + + hq2xLut = new Texture(Gdx.files.classpath("shaders/hq2x.tga")); fullscreenQuad = new Mesh( true, 4, 6, @@ -480,7 +487,14 @@ public class App implements ApplicationListener { VertexAttribute.ColorUnpacked(), VertexAttribute.TexCoords(0) ); - updateFullscreenQuad(scr.getWidth(), scr.getHeight()); + fullscreenQuad2x = new Mesh( + true, 4, 6, + VertexAttribute.Position(), + VertexAttribute.ColorUnpacked(), + VertexAttribute.TexCoords(0) + ); + updateFullscreenQuad(fullscreenQuad, scr.getWidth(), scr.getHeight()); + updateFullscreenQuad(fullscreenQuad2x, scr.getWidth(), scr.getHeight()); // set up renderer info variables renderer = Gdx.graphics.getGLVersion().getRendererString(); @@ -571,11 +585,39 @@ public class App implements ApplicationListener { } - shaderPassthruRGBA.bind(); - shaderPassthruRGBA.setUniformMatrix("u_projTrans", camera.combined); - shaderPassthruRGBA.setUniformi("u_texture", 0); - postProcessorOutFBO.getColorBufferTexture().bind(0); - fullscreenQuad.render(shaderPassthruRGBA, GL20.GL_TRIANGLES); + if (getConfigString("screenmagnifyingfilter").equals("hq2x")) { + int canvasWidth = scr.getWidth() * 2; + int canvasHeight = scr.getHeight() * 2; + + shaderHQ2x.bind(); + shaderHQ2x.setUniformMatrix("u_projTrans", camera.combined); + shaderHQ2x.setUniformi("u_lut", 1); + shaderHQ2x.setUniformi("u_texture", 0); + shaderHQ2x.setUniformf("u_textureSize", canvasWidth / 2f, canvasHeight / 2f); + hq2xLut.setFilter(Texture.TextureFilter.Nearest, Texture.TextureFilter.Nearest); + hq2xLut.bind(1); + postProcessorOutFBO.getColorBufferTexture().setFilter(Texture.TextureFilter.Nearest, Texture.TextureFilter.Nearest); + postProcessorOutFBO.getColorBufferTexture().bind(0); + fullscreenQuad2x.render(shaderHQ2x, GL20.GL_TRIANGLES); + } + else if (getConfigDouble("screenmagnifying") < 1.01 || getConfigString("screenmagnifyingfilter").equals("none")) { + shaderPassthruRGBA.bind(); + shaderPassthruRGBA.setUniformMatrix("u_projTrans", camera.combined); + shaderPassthruRGBA.setUniformi("u_texture", 0); + postProcessorOutFBO.getColorBufferTexture().setFilter(Texture.TextureFilter.Nearest, Texture.TextureFilter.Nearest); + postProcessorOutFBO.getColorBufferTexture().bind(0); + fullscreenQuad.render(shaderPassthruRGBA, GL20.GL_TRIANGLES); + } + else if (getConfigString("screenmagnifyingfilter").equals("bilinear")) { + shaderPassthruRGBA.bind(); + shaderPassthruRGBA.setUniformMatrix("u_projTrans", camera.combined); + shaderPassthruRGBA.setUniformi("u_texture", 0); + postProcessorOutFBO.getColorBufferTexture().setFilter(Texture.TextureFilter.Linear, Texture.TextureFilter.Linear); + postProcessorOutFBO.getColorBufferTexture().bind(0); + fullscreenQuad.render(shaderPassthruRGBA, GL20.GL_TRIANGLES); + } + + // process resize request if (resizeRequested) { @@ -708,7 +750,8 @@ public class App implements ApplicationListener { if (currentScreen != null) currentScreen.resize(scr.getWidth(), scr.getHeight()); TerrarumPostProcessor.INSTANCE.resize(scr.getWidth(), scr.getHeight()); - updateFullscreenQuad(scr.getWidth(), scr.getHeight()); + updateFullscreenQuad(fullscreenQuad, scr.getWidth(), scr.getHeight()); + updateFullscreenQuad(fullscreenQuad2x, scr.getWidth(), scr.getHeight()); if (renderFBO == null || @@ -772,9 +815,13 @@ public class App implements ApplicationListener { shaderColLUT.dispose(); shaderReflect.dispose(); shaderGhastlyWhite.dispose(); + shaderHQ2x.dispose(); + + hq2xLut.dispose(); CommonResourcePool.INSTANCE.dispose(); fullscreenQuad.dispose(); + fullscreenQuad2x.dispose(); logoBatch.dispose(); batch.dispose(); // shapeRender.dispose(); @@ -882,7 +929,6 @@ public class App implements ApplicationListener { shaderColLUT = loadShaderFromClasspath("shaders/default.vert", "shaders/passthrurgb.frag"); shaderGhastlyWhite = loadShaderFromClasspath("shaders/default.vert", "shaders/ghastlywhite.frag"); - // make gamepad(s) if (App.getConfigBoolean("usexinput")) { try { @@ -1036,14 +1082,14 @@ public class App implements ApplicationListener { logoBatch.setProjectionMatrix(camera.combined); } - private void updateFullscreenQuad(int WIDTH, int HEIGHT) { // NOT y-flipped quads! - fullscreenQuad.setVertices(new float[]{ + private void updateFullscreenQuad(Mesh mesh, int WIDTH, int HEIGHT) { // NOT y-flipped quads! + mesh.setVertices(new float[]{ 0f, 0f, 0f, 1f, 1f, 1f, 1f, 0f, 1f, WIDTH, 0f, 0f, 1f, 1f, 1f, 1f, 1f, 1f, WIDTH, HEIGHT, 0f, 1f, 1f, 1f, 1f, 1f, 0f, 0f, HEIGHT, 0f, 1f, 1f, 1f, 1f, 0f, 0f }); - fullscreenQuad.setIndices(new short[]{0, 1, 2, 2, 3, 0}); + mesh.setIndices(new short[]{0, 1, 2, 2, 3, 0}); } public static void setGamepadButtonLabels() { diff --git a/src/net/torvald/terrarum/DefaultConfig.kt b/src/net/torvald/terrarum/DefaultConfig.kt index cb9e0ab76..68393654b 100644 --- a/src/net/torvald/terrarum/DefaultConfig.kt +++ b/src/net/torvald/terrarum/DefaultConfig.kt @@ -112,6 +112,7 @@ object DefaultConfig { "inputmethod" to "none", "screenmagnifying" to 1.0, + "screenmagnifyingfilter" to "none", // "none", "bilinear", "hq2x" "fx_newlight" to false, diff --git a/src/net/torvald/terrarum/TerrarumPostProcessor.kt b/src/net/torvald/terrarum/TerrarumPostProcessor.kt index 0423d83c3..4571778a5 100644 --- a/src/net/torvald/terrarum/TerrarumPostProcessor.kt +++ b/src/net/torvald/terrarum/TerrarumPostProcessor.kt @@ -196,35 +196,35 @@ object TerrarumPostProcessor : Disposable { private fun Double.format(digits: Int) = "%.${digits}f".format(this) private fun Float.format(digits: Int) = "%.${digits}f".format(this) - private val swizzler = intArrayOf( - 1,0,0,0, 0,1,0,0, 0,0,1,0, 0,0,0,1, - 1,0,0,0, 0,1,0,0, 0,0,0,1, 0,0,1,0, - 1,0,0,0, 0,0,1,0, 0,1,0,0, 0,0,0,1, - 1,0,0,0, 0,0,1,0, 0,0,0,1, 0,1,0,0, - 1,0,0,0, 0,0,0,1, 0,1,0,0, 0,0,1,0, - 1,0,0,0, 0,0,0,1, 0,0,1,0, 0,1,0,0, + private val swizzler = floatArrayOf( + 1f,0f,0f,0f, 0f,1f,0f,0f, 0f,0f,1f,0f, 0f,0f,0f,1f, + 1f,0f,0f,0f, 0f,1f,0f,0f, 0f,0f,0f,1f, 0f,0f,1f,0f, + 1f,0f,0f,0f, 0f,0f,1f,0f, 0f,1f,0f,0f, 0f,0f,0f,1f, + 1f,0f,0f,0f, 0f,0f,1f,0f, 0f,0f,0f,1f, 0f,1f,0f,0f, + 1f,0f,0f,0f, 0f,0f,0f,1f, 0f,1f,0f,0f, 0f,0f,1f,0f, + 1f,0f,0f,0f, 0f,0f,0f,1f, 0f,0f,1f,0f, 0f,1f,0f,0f, - 0,1,0,0, 1,0,0,0, 0,0,1,0, 0,0,0,1, - 0,1,0,0, 1,0,0,0, 0,0,0,1, 0,0,1,0, - 0,1,0,0, 0,0,1,0, 1,0,0,0, 0,0,0,1, - 0,1,0,0, 0,0,1,0, 0,0,0,1, 1,0,0,0, - 0,1,0,0, 0,0,0,1, 1,0,0,0, 0,0,1,0, - 0,1,0,0, 0,0,0,1, 0,0,1,0, 1,0,0,0, + 0f,1f,0f,0f, 1f,0f,0f,0f, 0f,0f,1f,0f, 0f,0f,0f,1f, + 0f,1f,0f,0f, 1f,0f,0f,0f, 0f,0f,0f,1f, 0f,0f,1f,0f, + 0f,1f,0f,0f, 0f,0f,1f,0f, 1f,0f,0f,0f, 0f,0f,0f,1f, + 0f,1f,0f,0f, 0f,0f,1f,0f, 0f,0f,0f,1f, 1f,0f,0f,0f, + 0f,1f,0f,0f, 0f,0f,0f,1f, 1f,0f,0f,0f, 0f,0f,1f,0f, + 0f,1f,0f,0f, 0f,0f,0f,1f, 0f,0f,1f,0f, 1f,0f,0f,0f, - 0,0,1,0, 1,0,0,0, 0,1,0,0, 0,0,0,1, - 0,0,1,0, 1,0,0,0, 0,0,0,1, 0,1,0,0, - 0,0,1,0, 0,1,0,0, 1,0,0,0, 0,0,0,1, - 0,0,1,0, 0,1,0,0, 0,0,0,1, 1,0,0,0, - 0,0,1,0, 0,0,0,1, 1,0,0,0, 0,1,0,0, - 0,0,1,0, 0,0,0,1, 0,1,0,0, 1,0,0,0, + 0f,0f,1f,0f, 1f,0f,0f,0f, 0f,1f,0f,0f, 0f,0f,0f,1f, + 0f,0f,1f,0f, 1f,0f,0f,0f, 0f,0f,0f,1f, 0f,1f,0f,0f, + 0f,0f,1f,0f, 0f,1f,0f,0f, 1f,0f,0f,0f, 0f,0f,0f,1f, + 0f,0f,1f,0f, 0f,1f,0f,0f, 0f,0f,0f,1f, 1f,0f,0f,0f, + 0f,0f,1f,0f, 0f,0f,0f,1f, 1f,0f,0f,0f, 0f,1f,0f,0f, + 0f,0f,1f,0f, 0f,0f,0f,1f, 0f,1f,0f,0f, 1f,0f,0f,0f, - 0,0,0,1, 1,0,0,0, 0,1,0,0, 0,0,1,0, - 0,0,0,1, 1,0,0,0, 0,0,1,0, 0,1,0,0, - 0,0,0,1, 0,1,0,0, 1,0,0,0, 0,0,1,0, - 0,0,0,1, 0,1,0,0, 0,0,1,0, 1,0,0,0, - 0,0,0,1, 0,0,1,0, 1,0,0,0, 0,1,0,0, - 0,0,0,1, 0,0,1,0, 0,1,0,0, 1,0,0,0, - ).map { it.toFloat() }.toFloatArray() + 0f,0f,0f,1f, 1f,0f,0f,0f, 0f,1f,0f,0f, 0f,0f,1f,0f, + 0f,0f,0f,1f, 1f,0f,0f,0f, 0f,0f,1f,0f, 0f,1f,0f,0f, + 0f,0f,0f,1f, 0f,1f,0f,0f, 1f,0f,0f,0f, 0f,0f,1f,0f, + 0f,0f,0f,1f, 0f,1f,0f,0f, 0f,0f,1f,0f, 1f,0f,0f,0f, + 0f,0f,0f,1f, 0f,0f,1f,0f, 1f,0f,0f,0f, 0f,1f,0f,0f, + 0f,0f,0f,1f, 0f,0f,1f,0f, 0f,1f,0f,0f, 1f,0f,0f,0f, + ) private fun postShader(projMat: Matrix4, fbo: FrameBuffer) { diff --git a/src/net/torvald/terrarum/modulebasegame/ui/ControlPanelCommon.kt b/src/net/torvald/terrarum/modulebasegame/ui/ControlPanelCommon.kt index 5a8f09c46..1bc349e9a 100644 --- a/src/net/torvald/terrarum/modulebasegame/ui/ControlPanelCommon.kt +++ b/src/net/torvald/terrarum/modulebasegame/ui/ControlPanelCommon.kt @@ -6,6 +6,7 @@ import com.badlogic.gdx.graphics.Color import com.badlogic.gdx.graphics.g2d.SpriteBatch import net.torvald.terrarum.App import net.torvald.terrarum.CommonResourcePool +import net.torvald.terrarum.langpack.Lang import net.torvald.terrarum.ui.* import net.torvald.terrarumsansbitmap.gdx.TextureRegionPack @@ -26,9 +27,14 @@ object ControlPanelCommon { var CONFIG_SPINNER_WIDTH = 140 var CONFIG_TYPEIN_WIDTH = 240 var CONFIG_SLIDER_WIDTH = 240 + var CONFIG_TEXTSEL_WIDTH = 240 // @return Pair of - fun makeButton(parent: UICanvas, args: String, x: Int, y: Int, optionName: String): Pair Unit> { + fun makeButton(parent: UICanvas, args: String, x: Int, y: Int, optionNames0: String): Pair Unit> { + val optionNames = optionNames0.split(",") + val optionName = optionNames.first() + val arg = args.split(',') + return if (args.equals("h1") || args.equals("p")) { (object : UIItem(parent, x, y) { override val width = 1 @@ -44,8 +50,26 @@ object ControlPanelCommon { } } } + else if (args.startsWith("textsel,")) { + val labelFuns = arg.subList(1, arg.size).map { { Lang[it.substringAfter("=")] } } + val optionsList = arg.subList(1, arg.size).map { it.substringBefore("=") } + + val initialSel = optionsList.indexOf(App.getConfigString(optionName)) + + println("labelFuns = ${labelFuns.map { it.invoke() }}") + println("optionsList = $optionsList") + println("optionName = $optionName; value = ${App.getConfigString(optionName)}") + println("initialSel = $initialSel") + + if (initialSel < 0) throw IllegalArgumentException("config value '${App.getConfigString(optionName)}' for option '$optionName' is not found on the options list") + + UIItemTextSelector(parent, x, y, labelFuns, initialSel, CONFIG_TEXTSEL_WIDTH, clickToShowPalette = false) to { it: UIItem, optionStr: String -> + (it as UIItemTextSelector).selectionChangeListener = { + App.setConfig(optionStr, optionsList[it]) + } + } + } else if (args.startsWith("spinner,")) { - val arg = args.split(',') UIItemSpinner(parent, x, y, App.getConfigInt(optionName), arg[1].toInt(), arg[2].toInt(), arg[3].toInt(), CONFIG_SPINNER_WIDTH, numberToTextFunction = { "${it.toLong()}" }) to { it: UIItem, optionStr: String -> (it as UIItemSpinner).selectionChangeListener = { App.setConfig(optionStr, it) @@ -53,7 +77,6 @@ object ControlPanelCommon { } } else if (args.startsWith("spinnerd,")) { - val arg = args.split(',') UIItemSpinner(parent, x, y, App.getConfigDouble(optionName), arg[1].toDouble(), arg[2].toDouble(), arg[3].toDouble(), CONFIG_SPINNER_WIDTH, numberToTextFunction = { "${((it as Double)*100).toInt()}%" }) to { it: UIItem, optionStr: String -> (it as UIItemSpinner).selectionChangeListener = { App.setConfig(optionStr, it) @@ -61,7 +84,6 @@ object ControlPanelCommon { } } else if (args.startsWith("sliderd,")) { - val arg = args.split(',') UIItemHorzSlider(parent, x, y, App.getConfigDouble(optionName), arg[1].toDouble(), arg[2].toDouble(), CONFIG_SLIDER_WIDTH) to { it: UIItem, optionStr: String -> (it as UIItemHorzSlider).selectionChangeListener = { App.setConfig(optionStr, it) @@ -69,7 +91,6 @@ object ControlPanelCommon { } } else if (args.startsWith("spinnerimul,")) { - val arg = args.split(',') val mult = arg[4].toInt() UIItemSpinner(parent, x, y, App.getConfigInt(optionName) / mult, arg[1].toInt(), arg[2].toInt(), arg[3].toInt(), CONFIG_SPINNER_WIDTH, numberToTextFunction = { "${it.toLong()}" }) to { it: UIItem, optionStr: String -> (it as UIItemSpinner).selectionChangeListener = { @@ -90,8 +111,8 @@ object ControlPanelCommon { } } else if (args.startsWith("typeinres")) { - val keyWidth = optionName.substringBefore(',') - val keyHeight = optionName.substringAfter(',') + val keyWidth = optionNames[0] + val keyHeight = optionNames[1] UIItemTextLineInput(parent, x, y, CONFIG_SPINNER_WIDTH, defaultValue = { "${App.getConfigInt(keyWidth)}x${App.getConfigInt(keyHeight)}" }, maxLen = InputLenCap(9, InputLenCap.CharLenUnit.CODEPOINTS), diff --git a/src/net/torvald/terrarum/modulebasegame/ui/UIGraphicsControlPanel.kt b/src/net/torvald/terrarum/modulebasegame/ui/UIGraphicsControlPanel.kt index a73d98738..8bdc77aec 100644 --- a/src/net/torvald/terrarum/modulebasegame/ui/UIGraphicsControlPanel.kt +++ b/src/net/torvald/terrarum/modulebasegame/ui/UIGraphicsControlPanel.kt @@ -30,6 +30,7 @@ class UIGraphicsControlPanel(remoCon: UIRemoCon?) : UICanvas() { arrayOf("", { Lang["MENU_OPTIONS_DISPLAY"] }, "h1"), arrayOf("screenwidth,screenheight", { Lang["MENU_OPTIONS_RESOLUTION"] }, "typeinres"), arrayOf("screenmagnifying", { Lang["GAME_ACTION_ZOOM"] }, "spinnerd,1.0,2.0,0.05"), + arrayOf("screenmagnifyingfilter", { Lang["MENU_OPTIONS_FILTERING_MODE"] }, "textsel,none=MENU_OPTIONS_NONE,bilinear=MENU_OPTIONS_FILTERING_BILINEAR,hq2x=MENU_OPTIONS_FILTERING_HQ2X_DNT"), arrayOf("displayfps", { Lang["MENU_LABEL_FRAMESPERSEC"] }, "spinner,0,300,2"), arrayOf("usevsync", { Lang["MENU_OPTIONS_VSYNC"] }, "toggle"), arrayOf("", { "(${Lang["MENU_LABEL_RESTART_REQUIRED"]})" }, "p"), diff --git a/src/shaders/hq2x.frag b/src/shaders/hq2x.frag new file mode 100644 index 000000000..3282480f0 --- /dev/null +++ b/src/shaders/hq2x.frag @@ -0,0 +1,87 @@ +// This float value should be defined from the compiling code. +// #define SCALE [2, 3, 4].0 + +#version 150 +#ifdef GL_ES + #define PRECISION mediump + precision PRECISION float; + precision PRECISION int; +#else + #define PRECISION +#endif +#define SCALE 2.0 + +uniform sampler2D u_texture; +uniform sampler2D u_lut; +uniform vec2 u_textureSize; + +in vec4 v_texCoord[4]; + +const mat3 YUV_MATRIX = mat3(0.299, 0.587, 0.114, -0.169, -0.331, 0.5, 0.5, -0.419, -0.081); +const vec3 YUV_THRESHOLD = vec3(48.0/255.0, 7.0/255.0, 6.0/255.0); +const vec3 YUV_OFFSET = vec3(0, 0.5, 0.5); + +out vec4 fragColor; + +bool diff(vec3 yuv1, vec3 yuv2) { + return any(greaterThan(abs((yuv1 + YUV_OFFSET) - (yuv2 + YUV_OFFSET)), YUV_THRESHOLD)); +} + +mat3 transpose(mat3 val) { + mat3 result; + result[0][1] = val[1][0]; + result[0][2] = val[2][0]; + result[1][0] = val[0][1]; + result[1][2] = val[2][1]; + result[2][0] = val[0][2]; + result[2][1] = val[1][2]; + return result; +} + +void main() { + vec2 fp = fract(v_texCoord[0].xy * u_textureSize); + vec2 quad = sign(-0.5 + fp); + mat3 yuv = transpose(YUV_MATRIX); + + float dx = v_texCoord[0].z; + float dy = v_texCoord[0].w; + vec3 p1 = texture(u_texture, v_texCoord[0].xy).rgb; + vec3 p2 = texture(u_texture, v_texCoord[0].xy + vec2(dx, dy) * quad).rgb; + vec3 p3 = texture(u_texture, v_texCoord[0].xy + vec2(dx, 0) * quad).rgb; + vec3 p4 = texture(u_texture, v_texCoord[0].xy + vec2(0, dy) * quad).rgb; + // Use mat4 instead of mat4x3 here to support GLES. + mat4 pixels = mat4(vec4(p1, 0.0), vec4(p2, 0.0), vec4(p3, 0.0), vec4(p4, 0.0)); + + vec3 w1 = yuv * texture(u_texture, v_texCoord[1].xw).rgb; + vec3 w2 = yuv * texture(u_texture, v_texCoord[1].yw).rgb; + vec3 w3 = yuv * texture(u_texture, v_texCoord[1].zw).rgb; + + vec3 w4 = yuv * texture(u_texture, v_texCoord[2].xw).rgb; + vec3 w5 = yuv * p1; + vec3 w6 = yuv * texture(u_texture, v_texCoord[2].zw).rgb; + + vec3 w7 = yuv * texture(u_texture, v_texCoord[3].xw).rgb; + vec3 w8 = yuv * texture(u_texture, v_texCoord[3].yw).rgb; + vec3 w9 = yuv * texture(u_texture, v_texCoord[3].zw).rgb; + + bvec3 pattern[3]; + pattern[0] = bvec3(diff(w5, w1), diff(w5, w2), diff(w5, w3)); + pattern[1] = bvec3(diff(w5, w4), false , diff(w5, w6)); + pattern[2] = bvec3(diff(w5, w7), diff(w5, w8), diff(w5, w9)); + bvec4 cross = bvec4(diff(w4, w2), diff(w2, w6), diff(w8, w4), diff(w6, w8)); + + vec2 index; + index.x = dot(vec3(pattern[0]), vec3(1, 2, 4)) + + dot(vec3(pattern[1]), vec3(8, 0, 16)) + + dot(vec3(pattern[2]), vec3(32, 64, 128)); + index.y = dot(vec4(cross), vec4(1, 2, 4, 8)) * (SCALE * SCALE) + + dot(floor(fp * SCALE), vec2(1.0, SCALE)); + + vec2 step = vec2(1.0) / vec2(256.0, 16.0 * (SCALE * SCALE)); + vec2 offset = step / vec2(2.0); + vec4 weights = texture(u_lut, index * step + offset); + float sum = dot(weights, vec4(1)); + vec3 res = (pixels * (weights / sum)).rgb; + + fragColor.rgb = res; +} \ No newline at end of file diff --git a/src/shaders/hq2x.tga b/src/shaders/hq2x.tga new file mode 100644 index 000000000..6bac9d43d --- /dev/null +++ b/src/shaders/hq2x.tga @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:5fa8cb307db13cddc020963e3f71836f963807f117688fa2b3694d5f5275af5e +size 65554 diff --git a/src/shaders/hq2x.vert b/src/shaders/hq2x.vert new file mode 100644 index 000000000..352563587 --- /dev/null +++ b/src/shaders/hq2x.vert @@ -0,0 +1,41 @@ +#version 150 +#ifdef GL_ES + #define PRECISION mediump + precision PRECISION float; + precision PRECISION int; +#else + #define PRECISION +#endif + +in vec4 a_position; +in vec2 a_texCoord0; + +uniform mat4 u_projTrans; + +out vec2 u_textureSize; +out vec4 v_texCoord[4]; + +void main() { + gl_Position = u_projTrans * a_position; + + vec2 ps = 1.0/u_textureSize; + float dx = ps.x; + float dy = ps.y; + + // +----+----+----+ + // | | | | + // | w1 | w2 | w3 | + // +----+----+----+ + // | | | | + // | w4 | w5 | w6 | + // +----+----+----+ + // | | | | + // | w7 | w8 | w9 | + // +----+----+----+ + + v_texCoord[0].zw = ps; + v_texCoord[0].xy = a_texCoord0.xy; + v_texCoord[1] = a_texCoord0.xxxy + vec4(-dx, 0, dx, -dy); // w1 | w2 | w3 + v_texCoord[2] = a_texCoord0.xxxy + vec4(-dx, 0, dx, 0); // w4 | w5 | w6 + v_texCoord[3] = a_texCoord0.xxxy + vec4(-dx, 0, dx, dy); // w7 | w8 | w9 +} \ No newline at end of file