partially working hq2x, may not work on macOS tho

This commit is contained in:
minjaesong
2023-07-21 20:29:17 +09:00
parent 0af2e57368
commit 91d94d2dab
8 changed files with 268 additions and 96 deletions

View File

@@ -239,13 +239,11 @@ 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 Hq2x hq2x;
public static Mesh fullscreenQuad;
public static Mesh fullscreenQuad2x;
public static Mesh fullscreenQuad2d;
public static Mesh fullscreenQuadHQnX;
private static OrthographicCamera camera;
private static FlippingSpriteBatch logoBatch;
public static TextureRegion logo;
@@ -478,9 +476,7 @@ 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"));
hq2x = new Hq2x(2);
fullscreenQuad = new Mesh(
true, 4, 6,
@@ -494,7 +490,7 @@ public class App implements ApplicationListener {
VertexAttribute.ColorUnpacked(),
VertexAttribute.TexCoords(0)
);
fullscreenQuad2d = new Mesh(
fullscreenQuadHQnX = new Mesh(
true, 4, 6,
VertexAttribute.Position(),
VertexAttribute.ColorUnpacked(),
@@ -502,7 +498,7 @@ public class App implements ApplicationListener {
);
updateFullscreenQuad(fullscreenQuad, scr.getWidth(), scr.getHeight());
updateFullscreenQuad(fullscreenQuad2x, scr.getWidth() * 2, scr.getHeight() * 2);
updateFullscreenQuad(fullscreenQuad2d, scr.getWidth() / 2, scr.getHeight() / 2);
updateFullscreenQuad(fullscreenQuadHQnX, Math.round(scr.getWf() * 1280f / 2028f), Math.round(scr.getHf() * 720f / 982f));
// set up renderer info variables
renderer = Gdx.graphics.getGLVersion().getRendererString();
@@ -595,54 +591,17 @@ public class App implements ApplicationListener {
if (getConfigString("screenmagnifyingfilter").equals("hq2x") ) {
int canvasWidth = postProcessorOutFBO.getWidth(); // not zoomed dimension
int canvasHeight = postProcessorOutFBO.getHeight();
FrameBufferManager.begin(postProcessorOutFBO2);
shaderHQ2x.bind();
shaderHQ2x.setUniformMatrix("u_projTrans", camera.combined);
shaderHQ2x.setUniformi("u_lut", 1);
shaderHQ2x.setUniformi("u_texture", 0);
shaderHQ2x.setUniformf("u_textureSize", canvasWidth, canvasHeight);
hq2xLut.setFilter(Texture.TextureFilter.Nearest, Texture.TextureFilter.Nearest);
hq2xLut.setWrap(Texture.TextureWrap.Repeat, Texture.TextureWrap.Repeat);
hq2xLut.bind(1);
postProcessorOutFBO.getColorBufferTexture().setFilter(Texture.TextureFilter.Nearest, Texture.TextureFilter.Nearest);
postProcessorOutFBO.getColorBufferTexture().setWrap(Texture.TextureWrap.Repeat, Texture.TextureWrap.Repeat);
postProcessorOutFBO.getColorBufferTexture().bind(0);
fullscreenQuad.render(shaderHQ2x, GL20.GL_TRIANGLES); // the shader expects the target texture size to be 2x the input dimension
hq2x.renderToScreen(postProcessorOutFBO.getColorBufferTexture());
FrameBufferManager.end();
if (screenshotRequested) {
FrameBufferManager.begin(postProcessorOutFBO2);
try {
Pixmap p = Pixmap.createFromFrameBuffer(0, 0, postProcessorOutFBO2.getWidth(), postProcessorOutFBO2.getHeight());
PixmapIO.writePNG(Gdx.files.absolute(defaultDir+"/Screenshot-"+String.valueOf(System.currentTimeMillis())+".png"), p, 9, true);
p.dispose();
Terrarum.INSTANCE.getIngame().sendNotification("Screenshot taken");
}
catch (Throwable e) {
e.printStackTrace();
Terrarum.INSTANCE.getIngame().sendNotification("Failed to take screenshot: "+e.getMessage());
}
FrameBufferManager.end();
screenshotRequested = false;
}
shaderPassthruRGBA.bind();
shaderPassthruRGBA.setUniformMatrix("u_projTrans", camera.combined);
shaderPassthruRGBA.setUniformi("u_texture", 0);
postProcessorOutFBO2.getColorBufferTexture().setFilter(Texture.TextureFilter.Linear, Texture.TextureFilter.Linear);
postProcessorOutFBO2.getColorBufferTexture().setWrap(Texture.TextureWrap.Repeat, Texture.TextureWrap.Repeat);
postProcessorOutFBO2.getColorBufferTexture().bind(0);
fullscreenQuad.render(shaderPassthruRGBA, GL20.GL_TRIANGLES);
fullscreenQuadHQnX.render(shaderPassthruRGBA, GL20.GL_TRIANGLES);
}
else if (getConfigDouble("screenmagnifying") < 1.01 || getConfigString("screenmagnifyingfilter").equals("none")) {
shaderPassthruRGBA.bind();
@@ -796,7 +755,7 @@ public class App implements ApplicationListener {
TerrarumPostProcessor.INSTANCE.resize(scr.getWidth(), scr.getHeight());
updateFullscreenQuad(fullscreenQuad, scr.getWidth(), scr.getHeight());
updateFullscreenQuad(fullscreenQuad2x, scr.getWidth() * 2, scr.getHeight() * 2);
updateFullscreenQuad(fullscreenQuad2d, scr.getWidth() / 2, scr.getHeight() / 2);
updateFullscreenQuad(fullscreenQuadHQnX, Math.round(scr.getWf() * 1280f / 2028f), Math.round(scr.getHf() * 720f / 982f));
if (renderFBO == null ||
@@ -865,14 +824,12 @@ public class App implements ApplicationListener {
shaderColLUT.dispose();
shaderReflect.dispose();
shaderGhastlyWhite.dispose();
shaderHQ2x.dispose();
hq2xLut.dispose();
hq2x.dispose();
CommonResourcePool.INSTANCE.dispose();
fullscreenQuad.dispose();
fullscreenQuad2x.dispose();
fullscreenQuad2d.dispose();
fullscreenQuadHQnX.dispose();
logoBatch.dispose();
batch.dispose();
// shapeRender.dispose();

View File

@@ -0,0 +1,214 @@
package net.torvald.terrarum
import com.badlogic.gdx.Gdx
import com.badlogic.gdx.files.FileHandle
import com.badlogic.gdx.graphics.*
import com.badlogic.gdx.graphics.VertexAttributes.Usage
import com.badlogic.gdx.graphics.glutils.FrameBuffer
import com.badlogic.gdx.graphics.glutils.ShaderProgram
import com.badlogic.gdx.math.Matrix4
import com.badlogic.gdx.utils.Disposable
import com.badlogic.gdx.utils.GdxRuntimeException
/**
* [HQnX](https://en.wikipedia.org/wiki/Pixel-art_scaling_algorithms#hqnx_family)
* upscale algorithm GLSL implementation based on
* [CrossVR](https://github.com/CrossVR/hqx-shader/tree/master/glsl) project.
*/
class Hq2x : Disposable {
companion object {
private const val TEXTURE_HANDLE0 = 0
private const val TEXTURE_HANDLE1 = 1
private const val U_TEXTURE = "u_texture"
private const val U_LUT = "u_lut"
private const val U_TEXTURE_SIZE = "u_textureSize"
}
private val mesh = ViewportQuadMesh(
VertexAttribute(Usage.Position, 2, "a_position"),
VertexAttribute(Usage.TextureCoordinates, 2, "a_texCoord0"))
private val program: ShaderProgram
private val lutTexture: Texture
private val scaleFactor: Int
private var dstBuffer: FrameBuffer? = null
private var dstWidth = 0
private var dstHeight = 0
/** @param scaleFactor should be 2, 3 or 4 value. */
constructor(scaleFactor: Int) {
if (scaleFactor !in 2..4) {
throw GdxRuntimeException("Scale factor should be 2, 3 or 4.")
}
program = compileShader(
Gdx.files.classpath("shaders/hq2x.vert"),
Gdx.files.classpath("shaders/hq2x.frag"),
"#define SCALE ${scaleFactor}.0")
lutTexture = Texture(Gdx.files.classpath("shaders/hq${scaleFactor}x.png"))
this.scaleFactor = scaleFactor
}
override fun dispose() {
mesh.dispose()
program.dispose()
lutTexture.dispose()
dstBuffer?.dispose()
}
fun rebind() {
program.bind()
program.setUniformi(U_TEXTURE, TEXTURE_HANDLE0)
program.setUniformi(U_LUT, TEXTURE_HANDLE1)
program.setUniformf(U_TEXTURE_SIZE,
dstWidth / scaleFactor.toFloat(),
dstHeight / scaleFactor.toFloat())
}
fun renderToScreen(src: Texture) {
validate(src)
lutTexture.bind(TEXTURE_HANDLE1)
src.bind(TEXTURE_HANDLE0)
src.setFilter(Texture.TextureFilter.Nearest, Texture.TextureFilter.Nearest)
program.bind()
mesh.render(program)
}
fun renderToBuffer(src: Texture): Texture {
validate(src)
validateDstBuffer()
lutTexture.bind(TEXTURE_HANDLE1)
src.bind(TEXTURE_HANDLE0)
src.setFilter(Texture.TextureFilter.Nearest, Texture.TextureFilter.Nearest)
dstBuffer!!.begin()
program.bind()
mesh.render(program)
dstBuffer!!.end()
return dstBuffer!!.colorBufferTexture
}
private fun validate(src: Texture) {
val targetWidth = src.width * scaleFactor
val targetHeight = src.height * scaleFactor
// println("[Hq2x] $targetWidth x $targetHeight")
if (dstWidth != targetWidth || dstHeight != targetHeight) {
dstWidth = targetWidth
dstHeight = targetHeight
rebind()
}
}
private fun validateDstBuffer() {
if (dstBuffer == null || dstBuffer!!.width != dstWidth || dstBuffer!!.height != dstHeight) {
dstBuffer?.dispose()
dstBuffer = FrameBuffer(Pixmap.Format.RGB888, dstWidth, dstHeight, false)
dstBuffer!!.colorBufferTexture.setFilter(
Texture.TextureFilter.Nearest,
Texture.TextureFilter.Nearest)
}
}
}
/**
* Encapsulates a fullscreen quad mesh. Geometry is aligned to the viewport corners.
*
* @author bmanuel
* @author metaphore
*/
private class ViewportQuadMesh : Disposable {
companion object {
private const val VERT_SIZE = 16
private const val X1 = 0
private const val Y1 = 1
private const val U1 = 2
private const val V1 = 3
private const val X2 = 4
private const val Y2 = 5
private const val U2 = 6
private const val V2 = 7
private const val X3 = 8
private const val Y3 = 9
private const val U3 = 10
private const val V3 = 11
private const val X4 = 12
private const val Y4 = 13
private const val U4 = 14
private const val V4 = 15
private val verts: FloatArray
init {
verts = FloatArray(VERT_SIZE)
// Vertex coords
verts[X1] = -1f
verts[Y1] = -1f
verts[X2] = 1f
verts[Y2] = -1f
verts[X3] = 1f
verts[Y3] = 1f
verts[X4] = -1f
verts[Y4] = 1f
// Tex coords
verts[U1] = 0f
verts[V1] = 0f
verts[U2] = 1f
verts[V2] = 0f
verts[U3] = 1f
verts[V3] = 1f
verts[U4] = 0f
verts[V4] = 1f
}
}
private val mesh: Mesh
constructor(vararg attributes: VertexAttribute) {
mesh = Mesh(true, 4, 0, *attributes)
mesh.setVertices(verts)
}
override fun dispose() {
mesh.dispose()
}
/** Renders the quad with the specified shader program. */
fun render(program: ShaderProgram) {
mesh.render(program, GL20.GL_TRIANGLE_FAN, 0, 4)
}
}
private fun compileShader(vertexFile: FileHandle, fragmentFile: FileHandle, defines: String): ShaderProgram {
val sb = StringBuilder()
sb.append("Compiling \"").append(vertexFile.name()).append('/').append(fragmentFile.name()).append('\"')
if (defines.isNotEmpty()) {
sb.append(" w/ (").append(defines.replace("\n", ", ")).append(")")
}
sb.append("...")
Gdx.app.log("HqnxEffect", sb.toString())
val srcVert = vertexFile.readString()
val srcFrag = fragmentFile.readString()
val shader = ShaderProgram(
"$defines\n$srcVert".trimIndent(),
"$defines\n$srcFrag".trimIndent())
if (!shader.isCompiled) {
throw GdxRuntimeException("Shader compilation error: ${vertexFile.name()}/${fragmentFile.name()}\n${shader.log}")
}
return shader
}

View File

@@ -1,24 +1,24 @@
// This float value should be defined from the compiling code.
// #define SCALE [2, 3, 4].0
#version 150
#ifdef GL_ES
precision mediump float;
#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];
varying 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));
}
@@ -41,24 +41,24 @@ void main() {
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;
vec3 p1 = texture2D(u_texture, v_texCoord[0].xy).rgb;
vec3 p2 = texture2D(u_texture, v_texCoord[0].xy + vec2(dx, dy) * quad).rgb;
vec3 p3 = texture2D(u_texture, v_texCoord[0].xy + vec2(dx, 0) * quad).rgb;
vec3 p4 = texture2D(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 w1 = yuv * texture2D(u_texture, v_texCoord[1].xw).rgb;
vec3 w2 = yuv * texture2D(u_texture, v_texCoord[1].yw).rgb;
vec3 w3 = yuv * texture2D(u_texture, v_texCoord[1].zw).rgb;
vec3 w4 = yuv * texture(u_texture, v_texCoord[2].xw).rgb;
vec3 w4 = yuv * texture2D(u_texture, v_texCoord[2].xw).rgb;
vec3 w5 = yuv * p1;
vec3 w6 = yuv * texture(u_texture, v_texCoord[2].zw).rgb;
vec3 w6 = yuv * texture2D(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;
vec3 w7 = yuv * texture2D(u_texture, v_texCoord[3].xw).rgb;
vec3 w8 = yuv * texture2D(u_texture, v_texCoord[3].yw).rgb;
vec3 w9 = yuv * texture2D(u_texture, v_texCoord[3].zw).rgb;
bvec3 pattern[3];
pattern[0] = bvec3(diff(w5, w1), diff(w5, w2), diff(w5, w3));
@@ -75,13 +75,9 @@ void main() {
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);
vec4 weights = texture2D(u_lut, index * step + offset);
float sum = dot(weights, vec4(1));
vec3 res = (pixels * (weights / sum)).rgb;
fragColor = vec4(res.rgb, 1.0);
// fragColor = vec4(v_texCoord[0].x, v_texCoord[0].y, 0.0, 1.0);
gl_FragColor.rgb = res;
}

BIN
src/shaders/hq2x.png LFS Normal file

Binary file not shown.

Binary file not shown.

View File

@@ -1,26 +1,25 @@
#version 150
#ifdef GL_ES
precision mediump float;
#define PRECISION mediump
precision PRECISION float;
precision PRECISION int;
#else
#define PRECISION
#endif
#define SCALE 1.0
in vec4 a_position;
in vec2 a_texCoord0;
attribute vec4 a_position;
attribute vec2 a_texCoord0;
uniform mat4 u_projTrans;
uniform vec2 u_textureSize;
out vec2 u_textureSize;
out vec4 v_texCoord[4];
varying vec4 v_texCoord[4];
void main() {
gl_Position = u_projTrans * a_position / SCALE;
gl_Position = a_position;
vec2 ps = 1.0/u_textureSize;
float dx = ps.x;
float dy = ps.y;
vec2 a_texCoord00 = a_texCoord0 / SCALE;
// +----+----+----+
// | | | |
// | w1 | w2 | w3 |
@@ -33,8 +32,8 @@ void main() {
// +----+----+----+
v_texCoord[0].zw = ps;
v_texCoord[0].xy = a_texCoord00.xy;
v_texCoord[1] = a_texCoord00.xxxy + vec4(-dx, 0, dx, -dy); // w1 | w2 | w3
v_texCoord[2] = a_texCoord00.xxxy + vec4(-dx, 0, dx, 0); // w4 | w5 | w6
v_texCoord[3] = a_texCoord00.xxxy + vec4(-dx, 0, dx, dy); // w7 | w8 | w9
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
}

BIN
src/shaders/hq3x.png LFS Normal file

Binary file not shown.

BIN
src/shaders/hq4x.png LFS Normal file

Binary file not shown.