From 35df2ab2aa2056b61034757fe61554e7075dedd1 Mon Sep 17 00:00:00 2001 From: Vixid <52578495+VixidDev@users.noreply.github.com> Date: Mon, 19 Feb 2024 15:02:49 +0000 Subject: Added support for rendering rounded rectangles. #851 --- .../features/misc/RoundedRectangleShader.kt | 28 ++++++++++++ .../at/hannibal2/skyhanni/utils/RenderUtils.kt | 40 ++++++++++++++++- .../at/hannibal2/skyhanni/utils/shader/Shader.kt | 50 +++++++++++++++------- .../skyhanni/utils/shader/ShaderHelper.kt | 8 ++++ .../skyhanni/utils/shader/ShaderManager.kt | 34 ++++++++++++--- .../at/hannibal2/skyhanni/utils/shader/Uniform.kt | 10 ++++- .../assets/skyhanni/shaders/rounded_rect.fsh | 34 +++++++++++++++ .../assets/skyhanni/shaders/rounded_rect.vsh | 8 ++++ 8 files changed, 186 insertions(+), 26 deletions(-) create mode 100644 src/main/java/at/hannibal2/skyhanni/features/misc/RoundedRectangleShader.kt create mode 100644 src/main/resources/assets/skyhanni/shaders/rounded_rect.fsh create mode 100644 src/main/resources/assets/skyhanni/shaders/rounded_rect.vsh (limited to 'src/main') diff --git a/src/main/java/at/hannibal2/skyhanni/features/misc/RoundedRectangleShader.kt b/src/main/java/at/hannibal2/skyhanni/features/misc/RoundedRectangleShader.kt new file mode 100644 index 000000000..6b0ce5e63 --- /dev/null +++ b/src/main/java/at/hannibal2/skyhanni/features/misc/RoundedRectangleShader.kt @@ -0,0 +1,28 @@ +package at.hannibal2.skyhanni.features.misc + +import at.hannibal2.skyhanni.utils.shader.Shader +import at.hannibal2.skyhanni.utils.shader.Uniform +import net.minecraft.client.Minecraft + +object RoundedRectangleShader : Shader("rounded_rect", "rounded_rect") { + + val INSTANCE: RoundedRectangleShader + get() = this + + var scaleFactor: Float = 0f + var radius: Float = 0f + var smoothness: Float = 0f + var halfSize: FloatArray = floatArrayOf(0f, 0f) + var centerPos: FloatArray = floatArrayOf(0f, 0f) + set(value) { + field = floatArrayOf(value[0], Minecraft.getMinecraft().displayHeight - value[1]) + } + + override fun registerUniforms() { + registerUniform(Uniform.UniformType.FLOAT, "scaleFactor") { scaleFactor } + registerUniform(Uniform.UniformType.FLOAT, "radius") { radius } + registerUniform(Uniform.UniformType.FLOAT, "smoothness") { smoothness } + registerUniform(Uniform.UniformType.VEC2, "halfSize") { halfSize } + registerUniform(Uniform.UniformType.VEC2, "centerPos") { centerPos } + } +} \ No newline at end of file diff --git a/src/main/java/at/hannibal2/skyhanni/utils/RenderUtils.kt b/src/main/java/at/hannibal2/skyhanni/utils/RenderUtils.kt index 7c26c03e2..193beda2a 100644 --- a/src/main/java/at/hannibal2/skyhanni/utils/RenderUtils.kt +++ b/src/main/java/at/hannibal2/skyhanni/utils/RenderUtils.kt @@ -6,14 +6,18 @@ import at.hannibal2.skyhanni.data.GuiEditManager.Companion.getAbsX import at.hannibal2.skyhanni.data.GuiEditManager.Companion.getAbsY import at.hannibal2.skyhanni.events.GuiRenderItemEvent import at.hannibal2.skyhanni.events.LorenzRenderWorldEvent +import at.hannibal2.skyhanni.features.misc.RoundedRectangleShader import at.hannibal2.skyhanni.test.command.ErrorManager import at.hannibal2.skyhanni.utils.renderables.Renderable import at.hannibal2.skyhanni.utils.renderables.RenderableUtils.renderXAligned +import at.hannibal2.skyhanni.utils.shader.ShaderManager import io.github.moulberry.moulconfig.internal.TextRenderUtils import io.github.moulberry.notenoughupdates.util.Utils +import java.awt.Color import net.minecraft.client.Minecraft import net.minecraft.client.gui.FontRenderer import net.minecraft.client.gui.Gui +import net.minecraft.client.gui.ScaledResolution import net.minecraft.client.renderer.GlStateManager import net.minecraft.client.renderer.Tessellator import net.minecraft.client.renderer.vertex.DefaultVertexFormats @@ -25,7 +29,6 @@ import net.minecraft.util.AxisAlignedBB import net.minecraft.util.MathHelper import net.minecraft.util.ResourceLocation import org.lwjgl.opengl.GL11 -import java.awt.Color import kotlin.math.cos import kotlin.math.sin import kotlin.math.sqrt @@ -1130,4 +1133,39 @@ object RenderUtils { GlStateManager.enableLighting() GlStateManager.enableDepth() } + + /** + * Method to draw a rounded rectangle. + * + * **NOTE:** If you are using [GlStateManager.translate] or [GlStateManager.scale] + * with this method, ensure they are invoked in the correct order if you use both. That is, [GlStateManager.translate] + * is called **BEFORE** [GlStateManager.scale], otherwise the rectangle will not be rendered correctly + * + * @param color color of rect + * @param radius the radius of the corners (default 10) + * @param smoothness how smooth the corners will appear (default 2). NOTE: This does very + * little to the smoothness of the corners in reality due to how the final pixel color is calculated. + * It is best kept at its default. + */ + fun drawRoundRect(x: Int, y: Int, width: Int, height: Int, color: Int, radius: Int = 10, smoothness: Int = 1) { + val scaledRes = ScaledResolution(Minecraft.getMinecraft()) + val widthIn = width * scaledRes.scaleFactor + val heightIn = height * scaledRes.scaleFactor + val xIn = x * scaledRes.scaleFactor + val yIn = y * scaledRes.scaleFactor + + RoundedRectangleShader.scaleFactor = scaledRes.scaleFactor.toFloat() + RoundedRectangleShader.radius = radius.toFloat() + RoundedRectangleShader.smoothness = smoothness.toFloat() + RoundedRectangleShader.halfSize = floatArrayOf(widthIn / 2f, heightIn / 2f) + RoundedRectangleShader.centerPos = floatArrayOf(xIn + (widthIn / 2f), yIn + (heightIn / 2f)) + + GlStateManager.pushMatrix() + ShaderManager.enableShader("rounded_rect") + + Gui.drawRect(x, y, x + width, y + height, color) + + ShaderManager.disableShader() + GlStateManager.popMatrix() + } } diff --git a/src/main/java/at/hannibal2/skyhanni/utils/shader/Shader.kt b/src/main/java/at/hannibal2/skyhanni/utils/shader/Shader.kt index 7fb13ab86..6a95655ca 100644 --- a/src/main/java/at/hannibal2/skyhanni/utils/shader/Shader.kt +++ b/src/main/java/at/hannibal2/skyhanni/utils/shader/Shader.kt @@ -1,10 +1,12 @@ package at.hannibal2.skyhanni.utils.shader +import at.hannibal2.skyhanni.test.command.ErrorManager import at.hannibal2.skyhanni.utils.LorenzUtils +import java.util.function.Supplier import net.minecraft.client.shader.ShaderLinkHelper import org.apache.commons.lang3.StringUtils import org.lwjgl.opengl.GL11 -import java.util.function.Supplier +import org.lwjgl.opengl.OpenGLException /** * Superclass for shader objects to compile and attach vertex and fragment shaders to the shader program @@ -18,23 +20,39 @@ abstract class Shader(vertex: String, fragment: String) { var shaderProgram: Int = ShaderLinkHelper.getStaticShaderLinkHelper().createProgram() private val uniforms: MutableList> = mutableListOf() + var created = false + init { - val vertexShaderID = ShaderManager.loadShader(ShaderType.VERTEX, vertex) - ShaderManager.attachShader(shaderProgram, vertexShaderID) - val fragmentShaderID = ShaderManager.loadShader(ShaderType.FRAGMENT, fragment) - ShaderManager.attachShader(shaderProgram, fragmentShaderID) - - ShaderHelper.glLinkProgram(shaderProgram) - - val linkStatus = ShaderHelper.glGetProgrami(shaderProgram, ShaderHelper.GL_LINK_STATUS) - if (linkStatus == GL11.GL_FALSE) { - LorenzUtils.consoleLog( - "Error occurred when linking program with Vertex Shader: $vertex and Fragment Shader: $fragment : " + - StringUtils.trim(ShaderHelper.glGetProgramInfoLog(shaderProgram, 1024)) - ) - } + run { + val vertexShaderID = ShaderManager.loadShader(ShaderType.VERTEX, vertex).also { if (it == -1) return@run } + ShaderManager.attachShader(shaderProgram, vertexShaderID) + + val fragmentShaderID = ShaderManager.loadShader(ShaderType.FRAGMENT, fragment).also { if (it == -1) return@run } + ShaderManager.attachShader(shaderProgram, fragmentShaderID) - this.registerUniforms() + ShaderHelper.glLinkProgram(shaderProgram) + + if (ShaderHelper.glGetProgrami(shaderProgram, ShaderHelper.GL_LINK_STATUS) == GL11.GL_FALSE) { + val errorMessage = "Failed to link vertex shader $vertex and fragment shader $fragment. Features that " + + "utilise this shader will not work correctly, if at all." + val errorLog = StringUtils.trim(ShaderHelper.glGetShaderInfoLog(shaderProgram, 1024)) + + if (ShaderManager.inWorld()) { + ErrorManager.logErrorWithData( + OpenGLException("Shader linking error."), + errorMessage, + "Link Error:\n" to errorLog + ) + } else { + LorenzUtils.consoleLog("$errorMessage $errorLog") + } + + return@run + } + + this.registerUniforms() + created = true + } } abstract fun registerUniforms() diff --git a/src/main/java/at/hannibal2/skyhanni/utils/shader/ShaderHelper.kt b/src/main/java/at/hannibal2/skyhanni/utils/shader/ShaderHelper.kt index 6f24d0e89..6e36285b1 100644 --- a/src/main/java/at/hannibal2/skyhanni/utils/shader/ShaderHelper.kt +++ b/src/main/java/at/hannibal2/skyhanni/utils/shader/ShaderHelper.kt @@ -137,6 +137,14 @@ class ShaderHelper { if (USING_ARB_SHADERS) ARBShaderObjects.glUniform1fARB(location, v0) else GL20.glUniform1f(location, v0) } + fun glUniform2f(location: Int, v0: Float, v1: Float) { + if (USING_ARB_SHADERS) ARBShaderObjects.glUniform2fARB(location, v0, v1) else GL20.glUniform2f( + location, + v0, + v1 + ) + } + fun glUniform3f(location: Int, v0: Float, v1: Float, v2: Float) { if (USING_ARB_SHADERS) ARBShaderObjects.glUniform3fARB(location, v0, v1, v2) else GL20.glUniform3f( location, diff --git a/src/main/java/at/hannibal2/skyhanni/utils/shader/ShaderManager.kt b/src/main/java/at/hannibal2/skyhanni/utils/shader/ShaderManager.kt index 6df5c92f8..f9b5541c5 100644 --- a/src/main/java/at/hannibal2/skyhanni/utils/shader/ShaderManager.kt +++ b/src/main/java/at/hannibal2/skyhanni/utils/shader/ShaderManager.kt @@ -1,12 +1,15 @@ package at.hannibal2.skyhanni.utils.shader import at.hannibal2.skyhanni.features.chroma.ChromaShader +import at.hannibal2.skyhanni.features.misc.RoundedRectangleShader +import at.hannibal2.skyhanni.test.command.ErrorManager import at.hannibal2.skyhanni.utils.LorenzUtils +import java.io.BufferedReader +import java.io.InputStreamReader import net.minecraft.client.Minecraft import net.minecraft.util.ResourceLocation import org.apache.commons.lang3.StringUtils -import java.io.BufferedReader -import java.io.InputStreamReader +import org.lwjgl.opengl.OpenGLException /** * Object to handle shaders for SkyHanni @@ -19,12 +22,14 @@ object ShaderManager { */ enum class Shaders(val shader: Shader) { - CHROMA(ChromaShader.INSTANCE); + CHROMA(ChromaShader.INSTANCE), + ROUNDED_RECTANGLE(RoundedRectangleShader.INSTANCE); companion object { fun getShaderInstance(shaderName: String): Shader? = when (shaderName) { "chroma" -> CHROMA.shader + "rounded_rect" -> ROUNDED_RECTANGLE.shader else -> { null } @@ -44,6 +49,8 @@ object ShaderManager { shaders[shaderName] = shader } + if (!shader.created) return + activeShader = shader shader.enable() shader.updateUniforms() @@ -75,14 +82,27 @@ object ShaderManager { ShaderHelper.glCompileShader(shaderID) if (ShaderHelper.glGetShaderi(shaderID, ShaderHelper.GL_COMPILE_STATUS) == 0) { - LorenzUtils.consoleLog( - "Error occurred when compiling shader $fileName${type.extension} : " + - StringUtils.trim(ShaderHelper.glGetShaderInfoLog(shaderID, 1024)) - ) + val errorMessage = "Failed to compile shader $fileName${type.extension}. Features that utilise this " + + "shader will not work correctly, if at all." + val errorLog = StringUtils.trim(ShaderHelper.glGetShaderInfoLog(shaderID, 1024)) + + if (inWorld()) { + ErrorManager.logErrorWithData( + OpenGLException("Shader compilation error."), + errorMessage, + "GLSL Compilation Error:\n" to errorLog + ) + } else { + LorenzUtils.consoleLog("$errorMessage $errorLog") + } + + return -1 } return shaderID } + + fun inWorld() = (Minecraft.getMinecraft().theWorld != null) && (Minecraft.getMinecraft().thePlayer != null) } enum class ShaderType(val extension: String, val shaderType: Int) { diff --git a/src/main/java/at/hannibal2/skyhanni/utils/shader/Uniform.kt b/src/main/java/at/hannibal2/skyhanni/utils/shader/Uniform.kt index c9602d423..496d9c46c 100644 --- a/src/main/java/at/hannibal2/skyhanni/utils/shader/Uniform.kt +++ b/src/main/java/at/hannibal2/skyhanni/utils/shader/Uniform.kt @@ -21,6 +21,7 @@ class Uniform( companion object { val FLOAT: UniformType = UniformType() + val VEC2: UniformType = UniformType() val VEC3: UniformType = UniformType() val BOOL: UniformType = UniformType() } @@ -33,12 +34,17 @@ class Uniform( val newUniformValue: T = uniformValuesSupplier.get() if (!Objects.deepEquals(previousUniformValue, newUniformValue)) { when (uniformType) { - UniformType.FLOAT -> ShaderHelper.glUniform1f(uniformID, (newUniformValue as Float)) + UniformType.FLOAT -> { + ShaderHelper.glUniform1f(uniformID, (newUniformValue as Float)) + } + UniformType.VEC2 -> { + val values = newUniformValue as FloatArray + ShaderHelper.glUniform2f(uniformID, values[0], values[1]) + } UniformType.VEC3 -> { val values = newUniformValue as FloatArray ShaderHelper.glUniform3f(uniformID, values[0], values[1], values[2]) } - UniformType.BOOL -> ShaderHelper.glUniform1f(uniformID, if (newUniformValue as Boolean) 1f else 0f) } previousUniformValue = newUniformValue diff --git a/src/main/resources/assets/skyhanni/shaders/rounded_rect.fsh b/src/main/resources/assets/skyhanni/shaders/rounded_rect.fsh new file mode 100644 index 000000000..9f3c31786 --- /dev/null +++ b/src/main/resources/assets/skyhanni/shaders/rounded_rect.fsh @@ -0,0 +1,34 @@ +#version 130 + +uniform float scaleFactor; +uniform float radius; +uniform float smoothness; +uniform vec2 halfSize; +uniform vec2 centerPos; + +in vec4 color; + +// From https://www.shadertoy.com/view/WtdSDs +float roundedRectSDF(vec2 center, vec2 halfSize, float radius) { + return length(max(abs(center) - halfSize + radius, 0.0)) - radius; +} + +void main() { + float xScale = gl_ModelViewMatrix[0][0]; + float yScale = gl_ModelViewMatrix[1][1]; + float xTranslation = gl_ModelViewMatrix[3][0]; + float yTranslation = gl_ModelViewMatrix[3][1]; + + vec2 newHalfSize = vec2(halfSize.x * xScale, halfSize.y * yScale); + + float newCenterPosY = centerPos.y; + if (yScale > 1.0) { + newCenterPosY = centerPos.y - (halfSize.y * (yScale - 1)); + } + + vec2 newCenterPos = vec2((centerPos.x * xScale) + (xTranslation * scaleFactor), newCenterPosY - (yTranslation * scaleFactor)); + + float distance = roundedRectSDF(gl_FragCoord.xy - newCenterPos, newHalfSize, radius); + float smoothed = 1.0 - smoothstep(0.0, smoothness, distance); + gl_FragColor = color * vec4(1.0, 1.0, 1.0, smoothed); +} \ No newline at end of file diff --git a/src/main/resources/assets/skyhanni/shaders/rounded_rect.vsh b/src/main/resources/assets/skyhanni/shaders/rounded_rect.vsh new file mode 100644 index 000000000..beadbf5fb --- /dev/null +++ b/src/main/resources/assets/skyhanni/shaders/rounded_rect.vsh @@ -0,0 +1,8 @@ +#version 130 + +out vec4 color; + +void main() { + gl_Position = gl_ModelViewProjectionMatrix * gl_Vertex; + color = gl_Color; +} \ No newline at end of file -- cgit